Skip to main content

tl_interpreter/
lib.rs

1// ThinkingLanguage — Tree-Walking Interpreter
2// Licensed under MIT OR Apache-2.0
3//
4// Phase 0: Executes TL programs by walking the AST directly.
5// This is slow but correct — used for REPL and initial development.
6#![allow(clippy::large_enum_variant)]
7// Will be replaced by compiled execution in Phase 2.
8
9use std::collections::HashMap;
10use std::fmt;
11use std::sync::{Arc, Mutex, mpsc};
12use std::time::Duration;
13use tl_ast::*;
14use tl_data::datafusion::execution::FunctionRegistry;
15use tl_data::translate::{LocalValue, TranslateContext, translate_expr};
16use tl_data::{ArrowDataType, ArrowField, ArrowSchema, DataEngine, DataFrame, JoinType, col, lit};
17use tl_errors::security::SecurityPolicy;
18use tl_errors::{RuntimeError, TlError};
19use tl_stream::{ConnectorConfig, PipelineDef, PipelineRunner, PipelineStatus, StreamDef};
20
21/// Wrapper around DataFusion DataFrame that implements Debug + Clone.
22#[derive(Clone)]
23pub struct TlTable {
24    pub df: DataFrame,
25}
26
27impl fmt::Debug for TlTable {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "<table>")
30    }
31}
32
33/// Schema definition: column names and Arrow types.
34#[derive(Debug, Clone)]
35pub struct TlSchema {
36    pub name: String,
37    pub arrow_schema: Arc<ArrowSchema>,
38}
39
40/// Counter for generating unique task IDs in the interpreter.
41static INTERP_TASK_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
42/// Counter for generating unique channel IDs in the interpreter.
43static INTERP_CHANNEL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
44
45/// A spawned task handle for the interpreter.
46pub struct TlTask {
47    pub receiver: Mutex<Option<mpsc::Receiver<Result<Value, String>>>>,
48    pub id: u64,
49}
50
51impl TlTask {
52    pub fn new(receiver: mpsc::Receiver<Result<Value, String>>) -> Self {
53        TlTask {
54            receiver: Mutex::new(Some(receiver)),
55            id: INTERP_TASK_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
56        }
57    }
58}
59
60impl fmt::Debug for TlTask {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "<task {}>", self.id)
63    }
64}
65
66impl Clone for TlTask {
67    fn clone(&self) -> Self {
68        // Tasks are not truly cloneable (receiver is single-use), but we need
69        // Clone for Value. This creates a "consumed" copy.
70        TlTask {
71            receiver: Mutex::new(None),
72            id: self.id,
73        }
74    }
75}
76
77/// A channel for inter-task communication in the interpreter.
78pub struct TlChannel {
79    pub sender: mpsc::SyncSender<Value>,
80    pub receiver: Arc<Mutex<mpsc::Receiver<Value>>>,
81    pub id: u64,
82}
83
84impl TlChannel {
85    pub fn new(capacity: usize) -> Self {
86        let (tx, rx) = mpsc::sync_channel(capacity);
87        TlChannel {
88            sender: tx,
89            receiver: Arc::new(Mutex::new(rx)),
90            id: INTERP_CHANNEL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
91        }
92    }
93}
94
95impl Clone for TlChannel {
96    fn clone(&self) -> Self {
97        TlChannel {
98            sender: self.sender.clone(),
99            receiver: self.receiver.clone(),
100            id: self.id,
101        }
102    }
103}
104
105impl fmt::Debug for TlChannel {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(f, "<channel {}>", self.id)
108    }
109}
110
111/// Counter for generating unique generator IDs in the interpreter.
112static INTERP_GENERATOR_COUNTER: std::sync::atomic::AtomicU64 =
113    std::sync::atomic::AtomicU64::new(1);
114
115/// A generator for the interpreter — thread-based coroutine model.
116pub enum TlGeneratorKind {
117    /// User-defined generator using thread-based coroutines
118    UserDefined {
119        /// Receive yielded values from the generator thread
120        receiver: Mutex<Option<mpsc::Receiver<Result<Value, String>>>>,
121        /// Signal the generator thread to resume
122        resume_tx: mpsc::SyncSender<()>,
123    },
124    /// Built-in iterator over a list
125    ListIter {
126        items: Vec<Value>,
127        index: Mutex<usize>,
128    },
129    /// Take at most N items
130    Take {
131        source: Arc<TlGenerator>,
132        remaining: Mutex<usize>,
133    },
134    /// Skip first N items
135    Skip {
136        source: Arc<TlGenerator>,
137        remaining: Mutex<usize>,
138    },
139    /// Map a function over yielded values
140    Map {
141        source: Arc<TlGenerator>,
142        func: Value,
143    },
144    /// Filter values with predicate
145    Filter {
146        source: Arc<TlGenerator>,
147        func: Value,
148    },
149    /// Chain two generators
150    Chain {
151        first: Arc<TlGenerator>,
152        second: Arc<TlGenerator>,
153        on_second: Mutex<bool>,
154    },
155    /// Zip two generators
156    Zip {
157        first: Arc<TlGenerator>,
158        second: Arc<TlGenerator>,
159    },
160    /// Enumerate values with index
161    Enumerate {
162        source: Arc<TlGenerator>,
163        index: Mutex<usize>,
164    },
165}
166
167pub struct TlGenerator {
168    pub kind: TlGeneratorKind,
169    pub done: Mutex<bool>,
170    pub id: u64,
171}
172
173impl TlGenerator {
174    pub fn new(kind: TlGeneratorKind) -> Self {
175        TlGenerator {
176            kind,
177            done: Mutex::new(false),
178            id: INTERP_GENERATOR_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
179        }
180    }
181}
182
183impl fmt::Debug for TlGenerator {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        write!(f, "<generator {}>", self.id)
186    }
187}
188
189impl Clone for TlGenerator {
190    fn clone(&self) -> Self {
191        // Generators are not truly cloneable (channels are single-use)
192        // but we need Clone for Value. This creates a "consumed" copy.
193        TlGenerator {
194            kind: TlGeneratorKind::ListIter {
195                items: vec![],
196                index: Mutex::new(0),
197            },
198            done: Mutex::new(true),
199            id: self.id,
200        }
201    }
202}
203
204/// Wrapper around a Python object for storage in interpreter Value.
205#[cfg(feature = "python")]
206pub struct InterpPyObjectWrapper {
207    pub inner: pyo3::Py<pyo3::PyAny>,
208}
209
210#[cfg(feature = "python")]
211impl Clone for InterpPyObjectWrapper {
212    fn clone(&self) -> Self {
213        pyo3::Python::with_gil(|py| InterpPyObjectWrapper {
214            inner: self.inner.clone_ref(py),
215        })
216    }
217}
218
219#[cfg(feature = "python")]
220impl fmt::Debug for InterpPyObjectWrapper {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        use pyo3::prelude::*;
223        pyo3::Python::with_gil(|py| {
224            let obj = self.inner.bind(py);
225            match obj.repr() {
226                Ok(r) => write!(f, "{}", r),
227                Err(_) => write!(f, "<pyobject>"),
228            }
229        })
230    }
231}
232
233#[cfg(feature = "python")]
234impl fmt::Display for InterpPyObjectWrapper {
235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236        use pyo3::prelude::*;
237        pyo3::Python::with_gil(|py| {
238            let obj = self.inner.bind(py);
239            match obj.str() {
240                Ok(s) => write!(f, "{}", s),
241                Err(_) => write!(f, "<pyobject>"),
242            }
243        })
244    }
245}
246
247/// Runtime value
248#[derive(Debug, Clone)]
249pub enum Value {
250    Int(i64),
251    Float(f64),
252    String(String),
253    Bool(bool),
254    List(Vec<Value>),
255    None,
256    /// A function defined in TL code
257    Function {
258        name: String,
259        params: Vec<Param>,
260        body: Vec<Stmt>,
261        is_generator: bool,
262    },
263    /// A built-in function
264    Builtin(String),
265    /// A closure (anonymous function with captured environment)
266    Closure {
267        params: Vec<Param>,
268        body: ClosureBody,
269        captured_env: Vec<HashMap<String, Value>>,
270    },
271    /// A lazy DataFusion table (DataFrame)
272    Table(TlTable),
273    /// A schema definition
274    Schema(TlSchema),
275    /// A tensor (ndarray)
276    Tensor(tl_ai::TlTensor),
277    /// A trained model
278    Model(tl_ai::TlModel),
279    /// A connector configuration
280    Connector(ConnectorConfig),
281    /// A pipeline definition
282    Pipeline(PipelineDef),
283    /// A stream definition
284    Stream(StreamDef),
285    /// An agent definition (Phase 34)
286    Agent(tl_stream::AgentDef),
287    /// A struct type definition
288    StructDef {
289        name: String,
290        fields: Vec<String>,
291    },
292    /// A struct instance
293    StructInstance {
294        type_name: String,
295        fields: HashMap<String, Value>,
296    },
297    /// An enum type definition
298    EnumDef {
299        name: String,
300        variants: Vec<(String, usize)>, // (variant_name, field_count)
301    },
302    /// An enum instance
303    EnumInstance {
304        type_name: String,
305        variant: String,
306        fields: Vec<Value>,
307    },
308    /// A module (from import)
309    Module {
310        name: String,
311        exports: HashMap<String, Value>,
312    },
313    /// An ordered map (string keys)
314    Map(Vec<(String, Value)>),
315    /// A set (unique values)
316    Set(Vec<Value>),
317    /// A spawned task handle
318    Task(Arc<TlTask>),
319    /// A channel for inter-task communication
320    Channel(Arc<TlChannel>),
321    /// A generator (lazy iterator)
322    Generator(Arc<TlGenerator>),
323    /// A fixed-point decimal value (Phase 22)
324    Decimal(rust_decimal::Decimal),
325    /// A secret value with redacted display (Phase 23)
326    Secret(String),
327    /// An opaque Python object (feature-gated)
328    #[cfg(feature = "python")]
329    PyObject(Arc<InterpPyObjectWrapper>),
330    /// An MCP client connection handle
331    #[cfg(feature = "mcp")]
332    McpClient(Arc<tl_mcp::McpClient>),
333    /// Tombstone for a value consumed by pipe-move
334    Moved,
335    /// Read-only reference wrapper
336    Ref(Arc<Value>),
337}
338
339impl fmt::Display for Value {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        match self {
342            Value::Int(n) => write!(f, "{n}"),
343            Value::Float(n) => {
344                if n.fract() == 0.0 {
345                    write!(f, "{n:.1}")
346                } else {
347                    write!(f, "{n}")
348                }
349            }
350            Value::String(s) => write!(f, "{s}"),
351            Value::Bool(b) => write!(f, "{b}"),
352            Value::List(items) => {
353                write!(f, "[")?;
354                for (i, item) in items.iter().enumerate() {
355                    if i > 0 {
356                        write!(f, ", ")?;
357                    }
358                    write!(f, "{item}")?;
359                }
360                write!(f, "]")
361            }
362            Value::None => write!(f, "none"),
363            Value::Function { name, .. } => write!(f, "<fn {name}>"),
364            Value::Builtin(name) => write!(f, "<builtin {name}>"),
365            Value::Closure { .. } => write!(f, "<closure>"),
366            Value::Table(_) => write!(f, "<table>"),
367            Value::Schema(s) => write!(f, "<schema {}>", s.name),
368            Value::Tensor(t) => write!(f, "{t}"),
369            Value::Model(m) => write!(f, "{m}"),
370            Value::Connector(c) => write!(f, "{c}"),
371            Value::Pipeline(p) => write!(f, "{p}"),
372            Value::Stream(s) => write!(f, "{s}"),
373            Value::Agent(a) => write!(f, "<agent {}>", a.name),
374            Value::StructDef { name, .. } => write!(f, "<struct {name}>"),
375            Value::StructInstance { type_name, fields } => {
376                write!(f, "{type_name} {{ ")?;
377                for (i, (k, v)) in fields.iter().enumerate() {
378                    if i > 0 {
379                        write!(f, ", ")?;
380                    }
381                    write!(f, "{k}: {v}")?;
382                }
383                write!(f, " }}")
384            }
385            Value::EnumDef { name, .. } => write!(f, "<enum {name}>"),
386            Value::EnumInstance {
387                type_name,
388                variant,
389                fields,
390            } => {
391                write!(f, "{type_name}::{variant}")?;
392                if !fields.is_empty() {
393                    write!(f, "(")?;
394                    for (i, v) in fields.iter().enumerate() {
395                        if i > 0 {
396                            write!(f, ", ")?;
397                        }
398                        write!(f, "{v}")?;
399                    }
400                    write!(f, ")")?;
401                }
402                Ok(())
403            }
404            Value::Module { name, .. } => write!(f, "<module {name}>"),
405            Value::Map(pairs) => {
406                write!(f, "{{")?;
407                for (i, (k, v)) in pairs.iter().enumerate() {
408                    if i > 0 {
409                        write!(f, ", ")?;
410                    }
411                    write!(f, "{k}: {v}")?;
412                }
413                write!(f, "}}")
414            }
415            Value::Set(items) => {
416                write!(f, "set{{")?;
417                for (i, item) in items.iter().enumerate() {
418                    if i > 0 {
419                        write!(f, ", ")?;
420                    }
421                    write!(f, "{item}")?;
422                }
423                write!(f, "}}")
424            }
425            Value::Task(t) => write!(f, "<task {}>", t.id),
426            Value::Channel(c) => write!(f, "<channel {}>", c.id),
427            Value::Generator(g) => write!(f, "<generator {}>", g.id),
428            Value::Decimal(d) => write!(f, "{d}"),
429            Value::Secret(_) => write!(f, "***"),
430            #[cfg(feature = "python")]
431            Value::PyObject(w) => write!(f, "{w}"),
432            #[cfg(feature = "mcp")]
433            Value::McpClient(_) => write!(f, "<mcp_client>"),
434            Value::Moved => write!(f, "<moved>"),
435            Value::Ref(inner) => write!(f, "{inner}"),
436        }
437    }
438}
439
440impl Value {
441    pub fn is_truthy(&self) -> bool {
442        match self {
443            Value::Bool(b) => *b,
444            Value::Int(n) => *n != 0,
445            Value::Float(n) => *n != 0.0,
446            Value::String(s) => !s.is_empty(),
447            Value::List(items) => !items.is_empty(),
448            Value::Map(pairs) => !pairs.is_empty(),
449            Value::Decimal(d) => !d.is_zero(),
450            Value::Secret(s) => !s.is_empty(),
451            Value::None => false,
452            #[cfg(feature = "python")]
453            Value::PyObject(_) => true,
454            Value::Moved => false,
455            Value::Ref(inner) => inner.is_truthy(),
456            _ => true,
457        }
458    }
459
460    pub fn type_name(&self) -> &'static str {
461        match self {
462            Value::Int(_) => "int64",
463            Value::Float(_) => "float64",
464            Value::String(_) => "string",
465            Value::Bool(_) => "bool",
466            Value::List(_) => "list",
467            Value::None => "none",
468            Value::Function { .. } => "function",
469            Value::Builtin(_) => "builtin",
470            Value::Closure { .. } => "closure",
471            Value::Table(_) => "table",
472            Value::Schema(_) => "schema",
473            Value::Tensor(_) => "tensor",
474            Value::Model(_) => "model",
475            Value::Connector(_) => "connector",
476            Value::Pipeline(_) => "pipeline",
477            Value::Stream(_) => "stream",
478            Value::Agent(_) => "agent",
479            Value::StructDef { .. } => "struct_def",
480            Value::StructInstance { type_name, .. } => {
481                // We can't return a dynamic string from &'static str,
482                // so just return "struct"
483                let _ = type_name;
484                "struct"
485            }
486            Value::EnumDef { .. } => "enum_def",
487            Value::EnumInstance { .. } => "enum",
488            Value::Module { .. } => "module",
489            Value::Map(_) => "map",
490            Value::Set(_) => "set",
491            Value::Task(_) => "task",
492            Value::Channel(_) => "channel",
493            Value::Generator(_) => "generator",
494            Value::Decimal(_) => "decimal",
495            Value::Secret(_) => "secret",
496            #[cfg(feature = "python")]
497            Value::PyObject(_) => "pyobject",
498            #[cfg(feature = "mcp")]
499            Value::McpClient(_) => "mcp_client",
500            Value::Moved => "<moved>",
501            Value::Ref(inner) => inner.type_name(),
502        }
503    }
504}
505
506/// Control flow signals
507enum Signal {
508    None,
509    Return(Value),
510    Break,
511    Continue,
512    Throw(Value),
513    #[allow(dead_code)]
514    Yield(Value),
515}
516
517/// Generator-specific control flow signals (used in exec_stmt_gen).
518enum GenSignal {
519    None,
520    Return(Value),
521    Break,
522    Continue,
523    Throw(Value),
524    Yield(#[allow(dead_code)] Value),
525}
526
527/// Variable environment (scope chain)
528#[derive(Debug, Clone)]
529pub struct Environment {
530    scopes: Vec<HashMap<String, Value>>,
531}
532
533impl Environment {
534    pub fn new() -> Self {
535        let mut global = HashMap::new();
536        // Register builtins
537        global.insert("print".to_string(), Value::Builtin("print".to_string()));
538        global.insert("println".to_string(), Value::Builtin("println".to_string()));
539        global.insert("len".to_string(), Value::Builtin("len".to_string()));
540        global.insert("str".to_string(), Value::Builtin("str".to_string()));
541        global.insert("int".to_string(), Value::Builtin("int".to_string()));
542        global.insert("float".to_string(), Value::Builtin("float".to_string()));
543        global.insert("abs".to_string(), Value::Builtin("abs".to_string()));
544        global.insert("min".to_string(), Value::Builtin("min".to_string()));
545        global.insert("max".to_string(), Value::Builtin("max".to_string()));
546        global.insert("range".to_string(), Value::Builtin("range".to_string()));
547        global.insert("push".to_string(), Value::Builtin("push".to_string()));
548        global.insert("type_of".to_string(), Value::Builtin("type_of".to_string()));
549        global.insert("map".to_string(), Value::Builtin("map".to_string()));
550        global.insert("filter".to_string(), Value::Builtin("filter".to_string()));
551        global.insert("reduce".to_string(), Value::Builtin("reduce".to_string()));
552        global.insert("sum".to_string(), Value::Builtin("sum".to_string()));
553        global.insert("any".to_string(), Value::Builtin("any".to_string()));
554        global.insert("all".to_string(), Value::Builtin("all".to_string()));
555        // Data engine builtins
556        global.insert(
557            "read_csv".to_string(),
558            Value::Builtin("read_csv".to_string()),
559        );
560        global.insert(
561            "read_parquet".to_string(),
562            Value::Builtin("read_parquet".to_string()),
563        );
564        global.insert(
565            "write_csv".to_string(),
566            Value::Builtin("write_csv".to_string()),
567        );
568        global.insert(
569            "write_parquet".to_string(),
570            Value::Builtin("write_parquet".to_string()),
571        );
572        global.insert("collect".to_string(), Value::Builtin("collect".to_string()));
573        global.insert("show".to_string(), Value::Builtin("show".to_string()));
574        global.insert(
575            "describe".to_string(),
576            Value::Builtin("describe".to_string()),
577        );
578        global.insert("head".to_string(), Value::Builtin("head".to_string()));
579        global.insert(
580            "postgres".to_string(),
581            Value::Builtin("postgres".to_string()),
582        );
583        global.insert(
584            "read_postgres".to_string(),
585            Value::Builtin("postgres".to_string()),
586        );
587        global.insert(
588            "postgres_query".to_string(),
589            Value::Builtin("postgres_query".to_string()),
590        );
591        global.insert("fold".to_string(), Value::Builtin("fold".to_string()));
592        global.insert(
593            "tl_config_resolve".to_string(),
594            Value::Builtin("tl_config_resolve".to_string()),
595        );
596        // AI builtins
597        global.insert("tensor".to_string(), Value::Builtin("tensor".to_string()));
598        global.insert(
599            "tensor_zeros".to_string(),
600            Value::Builtin("tensor_zeros".to_string()),
601        );
602        global.insert(
603            "tensor_ones".to_string(),
604            Value::Builtin("tensor_ones".to_string()),
605        );
606        global.insert(
607            "tensor_shape".to_string(),
608            Value::Builtin("tensor_shape".to_string()),
609        );
610        global.insert(
611            "tensor_reshape".to_string(),
612            Value::Builtin("tensor_reshape".to_string()),
613        );
614        global.insert(
615            "tensor_transpose".to_string(),
616            Value::Builtin("tensor_transpose".to_string()),
617        );
618        global.insert(
619            "tensor_sum".to_string(),
620            Value::Builtin("tensor_sum".to_string()),
621        );
622        global.insert(
623            "tensor_mean".to_string(),
624            Value::Builtin("tensor_mean".to_string()),
625        );
626        global.insert(
627            "tensor_dot".to_string(),
628            Value::Builtin("tensor_dot".to_string()),
629        );
630        global.insert("predict".to_string(), Value::Builtin("predict".to_string()));
631        global.insert("embed".to_string(), Value::Builtin("embed".to_string()));
632        global.insert(
633            "similarity".to_string(),
634            Value::Builtin("similarity".to_string()),
635        );
636        global.insert(
637            "ai_complete".to_string(),
638            Value::Builtin("ai_complete".to_string()),
639        );
640        global.insert("ai_chat".to_string(), Value::Builtin("ai_chat".to_string()));
641        global.insert(
642            "model_save".to_string(),
643            Value::Builtin("model_save".to_string()),
644        );
645        global.insert(
646            "model_load".to_string(),
647            Value::Builtin("model_load".to_string()),
648        );
649        global.insert(
650            "model_register".to_string(),
651            Value::Builtin("model_register".to_string()),
652        );
653        global.insert(
654            "model_list".to_string(),
655            Value::Builtin("model_list".to_string()),
656        );
657        global.insert(
658            "model_get".to_string(),
659            Value::Builtin("model_get".to_string()),
660        );
661        // Streaming builtins
662        global.insert(
663            "alert_slack".to_string(),
664            Value::Builtin("alert_slack".to_string()),
665        );
666        global.insert(
667            "alert_webhook".to_string(),
668            Value::Builtin("alert_webhook".to_string()),
669        );
670        global.insert("emit".to_string(), Value::Builtin("emit".to_string()));
671        global.insert("lineage".to_string(), Value::Builtin("lineage".to_string()));
672        global.insert(
673            "run_pipeline".to_string(),
674            Value::Builtin("run_pipeline".to_string()),
675        );
676        // Math builtins
677        global.insert("sqrt".to_string(), Value::Builtin("sqrt".to_string()));
678        global.insert("pow".to_string(), Value::Builtin("pow".to_string()));
679        global.insert("floor".to_string(), Value::Builtin("floor".to_string()));
680        global.insert("ceil".to_string(), Value::Builtin("ceil".to_string()));
681        global.insert("round".to_string(), Value::Builtin("round".to_string()));
682        global.insert("sin".to_string(), Value::Builtin("sin".to_string()));
683        global.insert("cos".to_string(), Value::Builtin("cos".to_string()));
684        global.insert("tan".to_string(), Value::Builtin("tan".to_string()));
685        global.insert("log".to_string(), Value::Builtin("log".to_string()));
686        global.insert("log2".to_string(), Value::Builtin("log2".to_string()));
687        global.insert("log10".to_string(), Value::Builtin("log10".to_string()));
688        global.insert("join".to_string(), Value::Builtin("join".to_string()));
689        // Phase 6: Stdlib & Ecosystem
690        global.insert(
691            "json_parse".to_string(),
692            Value::Builtin("json_parse".to_string()),
693        );
694        global.insert(
695            "json_stringify".to_string(),
696            Value::Builtin("json_stringify".to_string()),
697        );
698        global.insert(
699            "map_from".to_string(),
700            Value::Builtin("map_from".to_string()),
701        );
702        global.insert(
703            "read_file".to_string(),
704            Value::Builtin("read_file".to_string()),
705        );
706        global.insert(
707            "write_file".to_string(),
708            Value::Builtin("write_file".to_string()),
709        );
710        global.insert(
711            "append_file".to_string(),
712            Value::Builtin("append_file".to_string()),
713        );
714        global.insert(
715            "file_exists".to_string(),
716            Value::Builtin("file_exists".to_string()),
717        );
718        global.insert(
719            "list_dir".to_string(),
720            Value::Builtin("list_dir".to_string()),
721        );
722        global.insert("env_get".to_string(), Value::Builtin("env_get".to_string()));
723        global.insert("env_set".to_string(), Value::Builtin("env_set".to_string()));
724        global.insert(
725            "regex_match".to_string(),
726            Value::Builtin("regex_match".to_string()),
727        );
728        global.insert(
729            "regex_find".to_string(),
730            Value::Builtin("regex_find".to_string()),
731        );
732        global.insert(
733            "regex_replace".to_string(),
734            Value::Builtin("regex_replace".to_string()),
735        );
736        global.insert("now".to_string(), Value::Builtin("now".to_string()));
737        global.insert(
738            "date_format".to_string(),
739            Value::Builtin("date_format".to_string()),
740        );
741        global.insert(
742            "date_parse".to_string(),
743            Value::Builtin("date_parse".to_string()),
744        );
745        global.insert("zip".to_string(), Value::Builtin("zip".to_string()));
746        global.insert(
747            "enumerate".to_string(),
748            Value::Builtin("enumerate".to_string()),
749        );
750        global.insert("bool".to_string(), Value::Builtin("bool".to_string()));
751        // Assert builtins
752        global.insert("assert".to_string(), Value::Builtin("assert".to_string()));
753        global.insert(
754            "assert_eq".to_string(),
755            Value::Builtin("assert_eq".to_string()),
756        );
757        global.insert(
758            "assert_table_eq".to_string(),
759            Value::Builtin("assert_table_eq".to_string()),
760        );
761        global.insert("today".to_string(), Value::Builtin("today".to_string()));
762        global.insert(
763            "date_add".to_string(),
764            Value::Builtin("date_add".to_string()),
765        );
766        global.insert(
767            "date_diff".to_string(),
768            Value::Builtin("date_diff".to_string()),
769        );
770        global.insert(
771            "date_trunc".to_string(),
772            Value::Builtin("date_trunc".to_string()),
773        );
774        global.insert(
775            "date_extract".to_string(),
776            Value::Builtin("date_extract".to_string()),
777        );
778        global.insert(
779            "extract".to_string(),
780            Value::Builtin("date_extract".to_string()),
781        );
782        // HTTP builtins
783        global.insert(
784            "http_get".to_string(),
785            Value::Builtin("http_get".to_string()),
786        );
787        global.insert(
788            "http_post".to_string(),
789            Value::Builtin("http_post".to_string()),
790        );
791        global.insert(
792            "http_request".to_string(),
793            Value::Builtin("http_request".to_string()),
794        );
795        global.insert(
796            "run_agent".to_string(),
797            Value::Builtin("run_agent".to_string()),
798        );
799        global.insert(
800            "stream_agent".to_string(),
801            Value::Builtin("stream_agent".to_string()),
802        );
803        // Concurrency builtins
804        global.insert("spawn".to_string(), Value::Builtin("spawn".to_string()));
805        global.insert("sleep".to_string(), Value::Builtin("sleep".to_string()));
806        global.insert("channel".to_string(), Value::Builtin("channel".to_string()));
807        global.insert("send".to_string(), Value::Builtin("send".to_string()));
808        global.insert("recv".to_string(), Value::Builtin("recv".to_string()));
809        global.insert(
810            "try_recv".to_string(),
811            Value::Builtin("try_recv".to_string()),
812        );
813        global.insert(
814            "await_all".to_string(),
815            Value::Builtin("await_all".to_string()),
816        );
817        global.insert("pmap".to_string(), Value::Builtin("pmap".to_string()));
818        global.insert("timeout".to_string(), Value::Builtin("timeout".to_string()));
819        // Phase 8: Iterators & Generators
820        global.insert("next".to_string(), Value::Builtin("next".to_string()));
821        global.insert(
822            "is_generator".to_string(),
823            Value::Builtin("is_generator".to_string()),
824        );
825        global.insert("iter".to_string(), Value::Builtin("iter".to_string()));
826        global.insert("take".to_string(), Value::Builtin("take".to_string()));
827        global.insert("skip".to_string(), Value::Builtin("skip".to_string()));
828        global.insert(
829            "gen_collect".to_string(),
830            Value::Builtin("gen_collect".to_string()),
831        );
832        global.insert("gen_map".to_string(), Value::Builtin("gen_map".to_string()));
833        global.insert(
834            "gen_filter".to_string(),
835            Value::Builtin("gen_filter".to_string()),
836        );
837        global.insert("chain".to_string(), Value::Builtin("chain".to_string()));
838        global.insert("gen_zip".to_string(), Value::Builtin("gen_zip".to_string()));
839        global.insert(
840            "gen_enumerate".to_string(),
841            Value::Builtin("gen_enumerate".to_string()),
842        );
843        // Phase 10: Result builtins
844        global.insert("Ok".to_string(), Value::Builtin("Ok".to_string()));
845        global.insert("Err".to_string(), Value::Builtin("Err".to_string()));
846        global.insert("is_ok".to_string(), Value::Builtin("is_ok".to_string()));
847        global.insert("is_err".to_string(), Value::Builtin("is_err".to_string()));
848        global.insert("unwrap".to_string(), Value::Builtin("unwrap".to_string()));
849        // Phase 10: Set builtins
850        global.insert(
851            "set_from".to_string(),
852            Value::Builtin("set_from".to_string()),
853        );
854        global.insert("set_add".to_string(), Value::Builtin("set_add".to_string()));
855        global.insert(
856            "set_remove".to_string(),
857            Value::Builtin("set_remove".to_string()),
858        );
859        global.insert(
860            "set_contains".to_string(),
861            Value::Builtin("set_contains".to_string()),
862        );
863        global.insert(
864            "set_union".to_string(),
865            Value::Builtin("set_union".to_string()),
866        );
867        global.insert(
868            "set_intersection".to_string(),
869            Value::Builtin("set_intersection".to_string()),
870        );
871        global.insert(
872            "set_difference".to_string(),
873            Value::Builtin("set_difference".to_string()),
874        );
875        // Phase 15: Data Quality & Connectors
876        global.insert(
877            "fill_null".to_string(),
878            Value::Builtin("fill_null".to_string()),
879        );
880        global.insert(
881            "drop_null".to_string(),
882            Value::Builtin("drop_null".to_string()),
883        );
884        global.insert("dedup".to_string(), Value::Builtin("dedup".to_string()));
885        global.insert("clamp".to_string(), Value::Builtin("clamp".to_string()));
886        global.insert("random".to_string(), Value::Builtin("random".to_string()));
887        global.insert(
888            "random_int".to_string(),
889            Value::Builtin("random_int".to_string()),
890        );
891        global.insert("sample".to_string(), Value::Builtin("sample".to_string()));
892        global.insert("exp".to_string(), Value::Builtin("exp".to_string()));
893        global.insert("is_nan".to_string(), Value::Builtin("is_nan".to_string()));
894        global.insert(
895            "is_infinite".to_string(),
896            Value::Builtin("is_infinite".to_string()),
897        );
898        global.insert("sign".to_string(), Value::Builtin("sign".to_string()));
899        global.insert(
900            "data_profile".to_string(),
901            Value::Builtin("data_profile".to_string()),
902        );
903        global.insert(
904            "row_count".to_string(),
905            Value::Builtin("row_count".to_string()),
906        );
907        global.insert(
908            "null_rate".to_string(),
909            Value::Builtin("null_rate".to_string()),
910        );
911        global.insert(
912            "is_unique".to_string(),
913            Value::Builtin("is_unique".to_string()),
914        );
915        global.insert(
916            "is_email".to_string(),
917            Value::Builtin("is_email".to_string()),
918        );
919        global.insert("is_url".to_string(), Value::Builtin("is_url".to_string()));
920        global.insert(
921            "is_phone".to_string(),
922            Value::Builtin("is_phone".to_string()),
923        );
924        global.insert(
925            "is_between".to_string(),
926            Value::Builtin("is_between".to_string()),
927        );
928        global.insert(
929            "levenshtein".to_string(),
930            Value::Builtin("levenshtein".to_string()),
931        );
932        global.insert("soundex".to_string(), Value::Builtin("soundex".to_string()));
933        global.insert(
934            "read_mysql".to_string(),
935            Value::Builtin("read_mysql".to_string()),
936        );
937        global.insert(
938            "read_sqlite".to_string(),
939            Value::Builtin("read_sqlite".to_string()),
940        );
941        global.insert(
942            "write_sqlite".to_string(),
943            Value::Builtin("write_sqlite".to_string()),
944        );
945        global.insert("duckdb".to_string(), Value::Builtin("duckdb".to_string()));
946        global.insert(
947            "read_duckdb".to_string(),
948            Value::Builtin("duckdb".to_string()),
949        );
950        global.insert(
951            "write_duckdb".to_string(),
952            Value::Builtin("write_duckdb".to_string()),
953        );
954        global.insert(
955            "redshift".to_string(),
956            Value::Builtin("redshift".to_string()),
957        );
958        global.insert(
959            "read_redshift".to_string(),
960            Value::Builtin("redshift".to_string()),
961        );
962        global.insert("mssql".to_string(), Value::Builtin("mssql".to_string()));
963        global.insert(
964            "read_mssql".to_string(),
965            Value::Builtin("mssql".to_string()),
966        );
967        global.insert(
968            "snowflake".to_string(),
969            Value::Builtin("snowflake".to_string()),
970        );
971        global.insert(
972            "bigquery".to_string(),
973            Value::Builtin("bigquery".to_string()),
974        );
975        global.insert(
976            "databricks".to_string(),
977            Value::Builtin("databricks".to_string()),
978        );
979        global.insert(
980            "clickhouse".to_string(),
981            Value::Builtin("clickhouse".to_string()),
982        );
983        global.insert("mongo".to_string(), Value::Builtin("mongo".to_string()));
984        global.insert(
985            "read_mongo".to_string(),
986            Value::Builtin("mongo".to_string()),
987        );
988        global.insert(
989            "read_mongodb".to_string(),
990            Value::Builtin("mongo".to_string()),
991        );
992        global.insert(
993            "sftp_download".to_string(),
994            Value::Builtin("sftp_download".to_string()),
995        );
996        global.insert(
997            "sftp_upload".to_string(),
998            Value::Builtin("sftp_upload".to_string()),
999        );
1000        global.insert(
1001            "sftp_list".to_string(),
1002            Value::Builtin("sftp_list".to_string()),
1003        );
1004        global.insert(
1005            "sftp_ls".to_string(),
1006            Value::Builtin("sftp_list".to_string()),
1007        );
1008        global.insert(
1009            "sftp_read_csv".to_string(),
1010            Value::Builtin("sftp_read_csv".to_string()),
1011        );
1012        global.insert(
1013            "sftp_read_parquet".to_string(),
1014            Value::Builtin("sftp_read_parquet".to_string()),
1015        );
1016        global.insert(
1017            "redis_connect".to_string(),
1018            Value::Builtin("redis_connect".to_string()),
1019        );
1020        global.insert(
1021            "redis_get".to_string(),
1022            Value::Builtin("redis_get".to_string()),
1023        );
1024        global.insert(
1025            "redis_set".to_string(),
1026            Value::Builtin("redis_set".to_string()),
1027        );
1028        global.insert(
1029            "redis_del".to_string(),
1030            Value::Builtin("redis_del".to_string()),
1031        );
1032        global.insert(
1033            "graphql_query".to_string(),
1034            Value::Builtin("graphql_query".to_string()),
1035        );
1036        global.insert(
1037            "register_s3".to_string(),
1038            Value::Builtin("register_s3".to_string()),
1039        );
1040        // Phase 20: Python FFI
1041        global.insert(
1042            "py_import".to_string(),
1043            Value::Builtin("py_import".to_string()),
1044        );
1045        global.insert("py_call".to_string(), Value::Builtin("py_call".to_string()));
1046        global.insert("py_eval".to_string(), Value::Builtin("py_eval".to_string()));
1047        global.insert(
1048            "py_getattr".to_string(),
1049            Value::Builtin("py_getattr".to_string()),
1050        );
1051        global.insert(
1052            "py_setattr".to_string(),
1053            Value::Builtin("py_setattr".to_string()),
1054        );
1055        global.insert(
1056            "py_to_tl".to_string(),
1057            Value::Builtin("py_to_tl".to_string()),
1058        );
1059        // Phase 21: Schema Evolution
1060        global.insert(
1061            "schema_register".to_string(),
1062            Value::Builtin("schema_register".to_string()),
1063        );
1064        global.insert(
1065            "schema_get".to_string(),
1066            Value::Builtin("schema_get".to_string()),
1067        );
1068        global.insert(
1069            "schema_latest".to_string(),
1070            Value::Builtin("schema_latest".to_string()),
1071        );
1072        global.insert(
1073            "schema_history".to_string(),
1074            Value::Builtin("schema_history".to_string()),
1075        );
1076        global.insert(
1077            "schema_check".to_string(),
1078            Value::Builtin("schema_check".to_string()),
1079        );
1080        global.insert(
1081            "schema_diff".to_string(),
1082            Value::Builtin("schema_diff".to_string()),
1083        );
1084        global.insert(
1085            "schema_versions".to_string(),
1086            Value::Builtin("schema_versions".to_string()),
1087        );
1088        global.insert(
1089            "schema_fields".to_string(),
1090            Value::Builtin("schema_fields".to_string()),
1091        );
1092        // Phase 22: Advanced Types
1093        global.insert("decimal".to_string(), Value::Builtin("decimal".to_string()));
1094        // Phase 23: Security & Access Control
1095        global.insert(
1096            "secret_get".to_string(),
1097            Value::Builtin("secret_get".to_string()),
1098        );
1099        global.insert(
1100            "secret_set".to_string(),
1101            Value::Builtin("secret_set".to_string()),
1102        );
1103        global.insert(
1104            "secret_delete".to_string(),
1105            Value::Builtin("secret_delete".to_string()),
1106        );
1107        global.insert(
1108            "secret_list".to_string(),
1109            Value::Builtin("secret_list".to_string()),
1110        );
1111        global.insert(
1112            "check_permission".to_string(),
1113            Value::Builtin("check_permission".to_string()),
1114        );
1115        global.insert(
1116            "mask_email".to_string(),
1117            Value::Builtin("mask_email".to_string()),
1118        );
1119        global.insert(
1120            "mask_phone".to_string(),
1121            Value::Builtin("mask_phone".to_string()),
1122        );
1123        global.insert("mask_cc".to_string(), Value::Builtin("mask_cc".to_string()));
1124        global.insert("redact".to_string(), Value::Builtin("redact".to_string()));
1125        global.insert("hash".to_string(), Value::Builtin("hash".to_string()));
1126        // Phase 25: Async builtins
1127        global.insert(
1128            "async_read_file".to_string(),
1129            Value::Builtin("async_read_file".to_string()),
1130        );
1131        global.insert(
1132            "async_write_file".to_string(),
1133            Value::Builtin("async_write_file".to_string()),
1134        );
1135        global.insert(
1136            "async_http_get".to_string(),
1137            Value::Builtin("async_http_get".to_string()),
1138        );
1139        global.insert(
1140            "async_http_post".to_string(),
1141            Value::Builtin("async_http_post".to_string()),
1142        );
1143        global.insert(
1144            "async_sleep".to_string(),
1145            Value::Builtin("async_sleep".to_string()),
1146        );
1147        global.insert("select".to_string(), Value::Builtin("select".to_string()));
1148        global.insert(
1149            "race_all".to_string(),
1150            Value::Builtin("race_all".to_string()),
1151        );
1152        global.insert(
1153            "async_map".to_string(),
1154            Value::Builtin("async_map".to_string()),
1155        );
1156        global.insert(
1157            "async_filter".to_string(),
1158            Value::Builtin("async_filter".to_string()),
1159        );
1160        // Phase 27: Data Error Hierarchy
1161        global.insert(
1162            "is_error".to_string(),
1163            Value::Builtin("is_error".to_string()),
1164        );
1165        global.insert(
1166            "error_type".to_string(),
1167            Value::Builtin("error_type".to_string()),
1168        );
1169        global.insert(
1170            "DataError".to_string(),
1171            Value::EnumDef {
1172                name: "DataError".to_string(),
1173                variants: vec![
1174                    ("ParseError".to_string(), 2),
1175                    ("SchemaError".to_string(), 3),
1176                    ("ValidationError".to_string(), 2),
1177                    ("NotFound".to_string(), 1),
1178                ],
1179            },
1180        );
1181        global.insert(
1182            "NetworkError".to_string(),
1183            Value::EnumDef {
1184                name: "NetworkError".to_string(),
1185                variants: vec![
1186                    ("ConnectionError".to_string(), 2),
1187                    ("TimeoutError".to_string(), 1),
1188                    ("HttpError".to_string(), 2),
1189                ],
1190            },
1191        );
1192        global.insert(
1193            "ConnectorError".to_string(),
1194            Value::EnumDef {
1195                name: "ConnectorError".to_string(),
1196                variants: vec![
1197                    ("AuthError".to_string(), 2),
1198                    ("QueryError".to_string(), 2),
1199                    ("ConfigError".to_string(), 2),
1200                ],
1201            },
1202        );
1203
1204        // MCP builtins
1205        #[cfg(feature = "mcp")]
1206        {
1207            global.insert(
1208                "mcp_connect".to_string(),
1209                Value::Builtin("mcp_connect".to_string()),
1210            );
1211            global.insert(
1212                "mcp_list_tools".to_string(),
1213                Value::Builtin("mcp_list_tools".to_string()),
1214            );
1215            global.insert(
1216                "mcp_call_tool".to_string(),
1217                Value::Builtin("mcp_call_tool".to_string()),
1218            );
1219            global.insert(
1220                "mcp_disconnect".to_string(),
1221                Value::Builtin("mcp_disconnect".to_string()),
1222            );
1223            global.insert(
1224                "mcp_serve".to_string(),
1225                Value::Builtin("mcp_serve".to_string()),
1226            );
1227            global.insert(
1228                "mcp_server_info".to_string(),
1229                Value::Builtin("mcp_server_info".to_string()),
1230            );
1231            global.insert(
1232                "mcp_ping".to_string(),
1233                Value::Builtin("mcp_ping".to_string()),
1234            );
1235            global.insert(
1236                "mcp_list_resources".to_string(),
1237                Value::Builtin("mcp_list_resources".to_string()),
1238            );
1239            global.insert(
1240                "mcp_read_resource".to_string(),
1241                Value::Builtin("mcp_read_resource".to_string()),
1242            );
1243            global.insert(
1244                "mcp_list_prompts".to_string(),
1245                Value::Builtin("mcp_list_prompts".to_string()),
1246            );
1247            global.insert(
1248                "mcp_get_prompt".to_string(),
1249                Value::Builtin("mcp_get_prompt".to_string()),
1250            );
1251        }
1252
1253        Self {
1254            scopes: vec![global],
1255        }
1256    }
1257
1258    pub fn push_scope(&mut self) {
1259        self.scopes.push(HashMap::new());
1260    }
1261
1262    pub fn pop_scope(&mut self) {
1263        self.scopes.pop();
1264    }
1265
1266    pub fn get(&self, name: &str) -> Option<&Value> {
1267        for scope in self.scopes.iter().rev() {
1268            if let Some(val) = scope.get(name) {
1269                return Some(val);
1270            }
1271        }
1272        None
1273    }
1274
1275    pub fn set(&mut self, name: String, value: Value) {
1276        if let Some(scope) = self.scopes.last_mut() {
1277            scope.insert(name, value);
1278        }
1279    }
1280
1281    /// Update an existing variable in the nearest scope that contains it
1282    pub fn update(&mut self, name: &str, value: Value) -> bool {
1283        for scope in self.scopes.iter_mut().rev() {
1284            if scope.contains_key(name) {
1285                scope.insert(name.to_string(), value);
1286                return true;
1287            }
1288        }
1289        false
1290    }
1291}
1292
1293impl Default for Environment {
1294    fn default() -> Self {
1295        Self::new()
1296    }
1297}
1298
1299/// The interpreter
1300pub struct Interpreter {
1301    pub env: Environment,
1302    /// Captured output (for testing)
1303    pub output: Vec<String>,
1304    /// Track last expression value for REPL display
1305    last_expr_value: Option<Value>,
1306    /// Data engine (lazily initialized)
1307    data_engine: Option<DataEngine>,
1308    /// Method table: type_name -> method_name -> function value
1309    method_table: HashMap<String, HashMap<String, Value>>,
1310    /// Module cache: file_path -> exported values
1311    module_cache: HashMap<String, HashMap<String, Value>>,
1312    /// Files currently being imported (for circular dependency detection)
1313    importing_files: std::collections::HashSet<String>,
1314    /// Current file path (for resolving relative imports)
1315    pub file_path: Option<String>,
1316    /// Whether we are in test mode (run test blocks)
1317    pub test_mode: bool,
1318    /// Package roots: package_name → source directory
1319    pub package_roots: HashMap<String, std::path::PathBuf>,
1320    /// Project root (where tl.toml lives)
1321    pub project_root: Option<std::path::PathBuf>,
1322    /// Schema registry for versioned schemas
1323    pub schema_registry: tl_compiler::schema::SchemaRegistry,
1324    /// Secret vault for credential management (Phase 23)
1325    pub secret_vault: HashMap<String, String>,
1326    /// Security policy for sandbox mode (Phase 23)
1327    pub security_policy: Option<SecurityPolicy>,
1328    /// Tokio runtime for async builtins (lazily initialized)
1329    #[cfg(feature = "async-runtime")]
1330    runtime: Option<Arc<tokio::runtime::Runtime>>,
1331    /// MCP clients associated with agents (agent_name -> clients)
1332    #[cfg(feature = "mcp")]
1333    mcp_agent_clients: HashMap<String, Vec<Arc<tl_mcp::McpClient>>>,
1334}
1335
1336impl Interpreter {
1337    pub fn new() -> Self {
1338        Self {
1339            env: Environment::new(),
1340            output: Vec::new(),
1341            last_expr_value: None,
1342            data_engine: None,
1343            method_table: HashMap::new(),
1344            module_cache: HashMap::new(),
1345            importing_files: std::collections::HashSet::new(),
1346            file_path: None,
1347            test_mode: false,
1348            package_roots: HashMap::new(),
1349            project_root: None,
1350            schema_registry: tl_compiler::schema::SchemaRegistry::new(),
1351            secret_vault: HashMap::new(),
1352            security_policy: None,
1353            #[cfg(feature = "async-runtime")]
1354            runtime: None,
1355            #[cfg(feature = "mcp")]
1356            mcp_agent_clients: HashMap::new(),
1357        }
1358    }
1359
1360    /// Lazily initialize and return the tokio runtime.
1361    #[cfg(feature = "async-runtime")]
1362    fn ensure_runtime(&mut self) -> Arc<tokio::runtime::Runtime> {
1363        if self.runtime.is_none() {
1364            self.runtime = Some(Arc::new(
1365                tokio::runtime::Builder::new_multi_thread()
1366                    .enable_all()
1367                    .build()
1368                    .expect("Failed to create tokio runtime"),
1369            ));
1370        }
1371        self.runtime.as_ref().unwrap().clone()
1372    }
1373
1374    /// Get or create the DataEngine (lazy init).
1375    fn engine(&mut self) -> &DataEngine {
1376        if self.data_engine.is_none() {
1377            self.data_engine = Some(DataEngine::new());
1378        }
1379        self.data_engine.as_ref().unwrap()
1380    }
1381
1382    /// Execute a complete program
1383    pub fn execute(&mut self, program: &Program) -> Result<Value, TlError> {
1384        let mut last = Value::None;
1385        for stmt in &program.statements {
1386            match self.exec_stmt(stmt)? {
1387                Signal::Return(val) => return Ok(val),
1388                Signal::None => {}
1389                Signal::Throw(val) => {
1390                    return Err(TlError::Runtime(RuntimeError {
1391                        message: format!("Unhandled throw: {val}"),
1392                        span: None,
1393                        stack_trace: vec![],
1394                    }));
1395                }
1396                Signal::Break | Signal::Continue => {
1397                    return Err(TlError::Runtime(RuntimeError {
1398                        message: "break/continue outside of loop".to_string(),
1399                        span: None,
1400                        stack_trace: vec![],
1401                    }));
1402                }
1403                Signal::Yield(_) => {} // yield outside generator is a no-op at top level
1404            }
1405            // Track last expression value for REPL
1406            if let StmtKind::Expr(_) = &stmt.kind {
1407                last = self.last_expr_value.clone().unwrap_or(Value::None);
1408            }
1409        }
1410        Ok(last)
1411    }
1412
1413    /// Execute a single statement (for REPL)
1414    pub fn execute_stmt(&mut self, stmt: &Stmt) -> Result<Value, TlError> {
1415        self.last_expr_value = None;
1416        match self.exec_stmt(stmt)? {
1417            Signal::Return(val) => Ok(val),
1418            _ => Ok(self.last_expr_value.clone().unwrap_or(Value::None)),
1419        }
1420    }
1421}
1422
1423impl Default for Interpreter {
1424    fn default() -> Self {
1425        Self::new()
1426    }
1427}
1428
1429// ── Statement execution ──────────────────────────────────
1430
1431impl Interpreter {
1432    fn exec_stmt(&mut self, stmt: &Stmt) -> Result<Signal, TlError> {
1433        match &stmt.kind {
1434            StmtKind::Let { name, value, .. } => {
1435                let val = self.eval_expr(value)?;
1436                self.env.set(name.clone(), val);
1437                Ok(Signal::None)
1438            }
1439            StmtKind::FnDecl {
1440                name,
1441                params,
1442                body,
1443                is_generator,
1444                ..
1445            } => {
1446                let func = Value::Function {
1447                    name: name.clone(),
1448                    params: params.clone(),
1449                    body: body.clone(),
1450                    is_generator: *is_generator,
1451                };
1452                self.env.set(name.clone(), func);
1453                Ok(Signal::None)
1454            }
1455            StmtKind::Expr(expr) => {
1456                let val = self.eval_expr(expr)?;
1457                self.last_expr_value = Some(val);
1458                Ok(Signal::None)
1459            }
1460            StmtKind::Return(expr) => {
1461                let val = match expr {
1462                    Some(e) => self.eval_expr(e)?,
1463                    None => Value::None,
1464                };
1465                Ok(Signal::Return(val))
1466            }
1467            StmtKind::If {
1468                condition,
1469                then_body,
1470                else_ifs,
1471                else_body,
1472            } => {
1473                let cond = self.eval_expr(condition)?;
1474                if cond.is_truthy() {
1475                    return self.exec_block(then_body);
1476                }
1477                for (cond_expr, body) in else_ifs {
1478                    let cond = self.eval_expr(cond_expr)?;
1479                    if cond.is_truthy() {
1480                        return self.exec_block(body);
1481                    }
1482                }
1483                if let Some(body) = else_body {
1484                    return self.exec_block(body);
1485                }
1486                Ok(Signal::None)
1487            }
1488            StmtKind::While { condition, body } => {
1489                loop {
1490                    let cond = self.eval_expr(condition)?;
1491                    if !cond.is_truthy() {
1492                        break;
1493                    }
1494                    match self.exec_block(body)? {
1495                        Signal::Break => break,
1496                        Signal::Return(v) => return Ok(Signal::Return(v)),
1497                        Signal::Throw(v) => return Ok(Signal::Throw(v)),
1498                        Signal::Continue | Signal::None | Signal::Yield(_) => continue,
1499                    }
1500                }
1501                Ok(Signal::None)
1502            }
1503            StmtKind::For { name, iter, body } | StmtKind::ParallelFor { name, iter, body } => {
1504                let iter_val = self.eval_expr(iter)?;
1505                // Handle generator iteration
1506                if let Value::Generator(ref g) = iter_val {
1507                    let g = g.clone();
1508                    loop {
1509                        let val = self.interpreter_next(&g)?;
1510                        if matches!(val, Value::None) {
1511                            break;
1512                        }
1513                        self.env.push_scope();
1514                        self.env.set(name.clone(), val);
1515                        let signal = self.exec_block(body)?;
1516                        self.env.pop_scope();
1517                        match signal {
1518                            Signal::Break => break,
1519                            Signal::Return(v) => return Ok(Signal::Return(v)),
1520                            Signal::Throw(v) => return Ok(Signal::Throw(v)),
1521                            Signal::Continue | Signal::None => continue,
1522                            _ => {}
1523                        }
1524                    }
1525                    return Ok(Signal::None);
1526                }
1527                let items = match iter_val {
1528                    Value::List(items) => items,
1529                    Value::Map(pairs) => {
1530                        // Map iteration yields [key, value] pairs
1531                        pairs
1532                            .into_iter()
1533                            .map(|(k, v)| Value::List(vec![Value::String(k), v]))
1534                            .collect()
1535                    }
1536                    Value::Set(items) => items,
1537                    _ => {
1538                        return Err(TlError::Runtime(RuntimeError {
1539                            message: format!("Cannot iterate over {}", iter_val.type_name()),
1540                            span: None,
1541                            stack_trace: vec![],
1542                        }));
1543                    }
1544                };
1545                for item in items {
1546                    self.env.push_scope();
1547                    self.env.set(name.clone(), item);
1548                    let signal = self.exec_block(body)?;
1549                    self.env.pop_scope();
1550                    match signal {
1551                        Signal::Break => break,
1552                        Signal::Return(v) => return Ok(Signal::Return(v)),
1553                        Signal::Throw(v) => return Ok(Signal::Throw(v)),
1554                        Signal::Continue | Signal::None | Signal::Yield(_) => continue,
1555                    }
1556                }
1557                Ok(Signal::None)
1558            }
1559            StmtKind::Schema {
1560                name,
1561                fields,
1562                version,
1563                ..
1564            } => {
1565                let arrow_fields: Vec<ArrowField> = fields
1566                    .iter()
1567                    .map(|f| {
1568                        let dt = tl_type_to_arrow(&f.type_ann);
1569                        ArrowField::new(&f.name, dt, true)
1570                    })
1571                    .collect();
1572                let arrow_schema = Arc::new(ArrowSchema::new(arrow_fields));
1573
1574                // If versioned, register in schema registry
1575                if let Some(ver) = version {
1576                    let mut metadata = tl_compiler::schema::SchemaMetadata::default();
1577                    // Extract @since from field doc comments
1578                    for f in fields {
1579                        if let Some(ref doc) = f.doc_comment {
1580                            for line in doc.lines() {
1581                                let trimmed = line.trim();
1582                                if let Some(rest) = trimmed.strip_prefix("@since") {
1583                                    if let Ok(v) = rest.trim().parse::<i64>() {
1584                                        metadata.field_since.insert(f.name.clone(), v);
1585                                    }
1586                                } else if let Some(rest) = trimmed.strip_prefix("@deprecated")
1587                                    && let Ok(v) = rest.trim().parse::<i64>()
1588                                {
1589                                    metadata.field_deprecated.insert(f.name.clone(), v);
1590                                }
1591                            }
1592                        }
1593                        if let Some(ref _def) = f.default_value {
1594                            metadata
1595                                .field_defaults
1596                                .insert(f.name.clone(), format!("{:?}", f.default_value));
1597                        }
1598                    }
1599                    let _ =
1600                        self.schema_registry
1601                            .register(name, *ver, arrow_schema.clone(), metadata);
1602                }
1603
1604                let schema = TlSchema {
1605                    name: name.clone(),
1606                    arrow_schema,
1607                };
1608                self.env.set(name.clone(), Value::Schema(schema));
1609                Ok(Signal::None)
1610            }
1611            StmtKind::Train {
1612                name,
1613                algorithm,
1614                config,
1615            } => self.exec_train(name, algorithm, config),
1616            StmtKind::Pipeline {
1617                name,
1618                extract,
1619                transform,
1620                load,
1621                schedule,
1622                timeout,
1623                retries,
1624                on_failure,
1625                on_success,
1626            } => self.exec_pipeline(
1627                name, extract, transform, load, schedule, timeout, retries, on_failure, on_success,
1628            ),
1629            StmtKind::StreamDecl {
1630                name,
1631                source,
1632                transform,
1633                sink,
1634                window,
1635                watermark,
1636            } => self.exec_stream_decl(name, source, transform, sink, window, watermark),
1637            StmtKind::Agent {
1638                name,
1639                model,
1640                system_prompt,
1641                tools,
1642                max_turns,
1643                temperature,
1644                max_tokens,
1645                base_url,
1646                api_key,
1647                output_format,
1648                on_tool_call,
1649                on_complete,
1650                mcp_servers,
1651            } => self.exec_agent(
1652                name,
1653                model,
1654                system_prompt,
1655                tools,
1656                max_turns,
1657                temperature,
1658                max_tokens,
1659                base_url,
1660                api_key,
1661                output_format,
1662                on_tool_call,
1663                on_complete,
1664                mcp_servers,
1665            ),
1666            StmtKind::SourceDecl {
1667                name,
1668                connector_type,
1669                config,
1670            } => self.exec_source_decl(name, connector_type, config),
1671            StmtKind::SinkDecl {
1672                name,
1673                connector_type,
1674                config,
1675            } => self.exec_sink_decl(name, connector_type, config),
1676            StmtKind::StructDecl { name, fields, .. } => {
1677                let field_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1678                self.env.set(
1679                    name.clone(),
1680                    Value::StructDef {
1681                        name: name.clone(),
1682                        fields: field_names,
1683                    },
1684                );
1685                Ok(Signal::None)
1686            }
1687            StmtKind::EnumDecl { name, variants, .. } => {
1688                let variant_info: Vec<(String, usize)> = variants
1689                    .iter()
1690                    .map(|v| (v.name.clone(), v.fields.len()))
1691                    .collect();
1692                self.env.set(
1693                    name.clone(),
1694                    Value::EnumDef {
1695                        name: name.clone(),
1696                        variants: variant_info,
1697                    },
1698                );
1699                Ok(Signal::None)
1700            }
1701            StmtKind::ImplBlock {
1702                type_name, methods, ..
1703            } => {
1704                let mut method_map = self.method_table.remove(type_name).unwrap_or_default();
1705                for method in methods {
1706                    if let StmtKind::FnDecl {
1707                        name,
1708                        params,
1709                        body,
1710                        is_generator,
1711                        ..
1712                    } = &method.kind
1713                    {
1714                        method_map.insert(
1715                            name.clone(),
1716                            Value::Function {
1717                                name: name.clone(),
1718                                params: params.clone(),
1719                                body: body.clone(),
1720                                is_generator: *is_generator,
1721                            },
1722                        );
1723                    }
1724                }
1725                self.method_table.insert(type_name.clone(), method_map);
1726                Ok(Signal::None)
1727            }
1728            StmtKind::TryCatch {
1729                try_body,
1730                catch_var,
1731                catch_body,
1732                finally_body,
1733            } => {
1734                self.env.push_scope();
1735                let mut result = Signal::None;
1736                let mut caught = None;
1737                for stmt in try_body {
1738                    match self.exec_stmt(stmt) {
1739                        Ok(Signal::Throw(val)) => {
1740                            caught = Some(val);
1741                            break;
1742                        }
1743                        Ok(Signal::Return(v)) => {
1744                            self.env.pop_scope();
1745                            // Run finally before returning
1746                            if let Some(finally_stmts) = finally_body {
1747                                self.env.push_scope();
1748                                for fs in finally_stmts {
1749                                    let _ = self.exec_stmt(fs);
1750                                }
1751                                self.env.pop_scope();
1752                            }
1753                            return Ok(Signal::Return(v));
1754                        }
1755                        Ok(sig) => {
1756                            result = sig;
1757                            if matches!(result, Signal::Break | Signal::Continue) {
1758                                break;
1759                            }
1760                        }
1761                        Err(TlError::Runtime(re)) => {
1762                            caught = Some(Value::String(re.message.clone()));
1763                            break;
1764                        }
1765                        Err(e) => {
1766                            self.env.pop_scope();
1767                            // Run finally before propagating error
1768                            if let Some(finally_stmts) = finally_body {
1769                                self.env.push_scope();
1770                                for fs in finally_stmts {
1771                                    let _ = self.exec_stmt(fs);
1772                                }
1773                                self.env.pop_scope();
1774                            }
1775                            return Err(e);
1776                        }
1777                    }
1778                }
1779                self.env.pop_scope();
1780                if let Some(err_val) = caught {
1781                    self.env.push_scope();
1782                    self.env.set(catch_var.clone(), err_val);
1783                    for stmt in catch_body {
1784                        match self.exec_stmt(stmt)? {
1785                            Signal::Return(v) => {
1786                                self.env.pop_scope();
1787                                // Run finally before returning
1788                                if let Some(finally_stmts) = finally_body {
1789                                    self.env.push_scope();
1790                                    for fs in finally_stmts {
1791                                        let _ = self.exec_stmt(fs);
1792                                    }
1793                                    self.env.pop_scope();
1794                                }
1795                                return Ok(Signal::Return(v));
1796                            }
1797                            Signal::Break | Signal::Continue => {
1798                                let sig = self.exec_stmt(stmt)?;
1799                                self.env.pop_scope();
1800                                if let Some(finally_stmts) = finally_body {
1801                                    self.env.push_scope();
1802                                    for fs in finally_stmts {
1803                                        let _ = self.exec_stmt(fs);
1804                                    }
1805                                    self.env.pop_scope();
1806                                }
1807                                return Ok(sig);
1808                            }
1809                            _ => {}
1810                        }
1811                    }
1812                    self.env.pop_scope();
1813                }
1814                // Run finally in normal flow
1815                if let Some(finally_stmts) = finally_body {
1816                    self.env.push_scope();
1817                    for fs in finally_stmts {
1818                        if let Signal::Return(v) = self.exec_stmt(fs)? {
1819                            self.env.pop_scope();
1820                            return Ok(Signal::Return(v));
1821                        }
1822                    }
1823                    self.env.pop_scope();
1824                }
1825                Ok(result)
1826            }
1827            StmtKind::Throw(expr) => {
1828                let val = self.eval_expr(expr)?;
1829                Ok(Signal::Throw(val))
1830            }
1831            StmtKind::Import { path, alias } => self.exec_import(path, alias.as_deref()),
1832            StmtKind::Test { name, body } => {
1833                if self.test_mode {
1834                    self.env.push_scope();
1835                    let mut failed = false;
1836                    for stmt in body {
1837                        match self.exec_stmt(stmt) {
1838                            Ok(Signal::Throw(val)) => {
1839                                let msg = format!("Test '{}' failed: throw {}", name, val);
1840                                self.output.push(msg.clone());
1841                                println!("{msg}");
1842                                failed = true;
1843                                break;
1844                            }
1845                            Ok(Signal::Return(_)) => break,
1846                            Ok(_) => {}
1847                            Err(e) => {
1848                                let msg = format!("Test '{}' failed: {}", name, e);
1849                                self.output.push(msg.clone());
1850                                println!("{msg}");
1851                                failed = true;
1852                                break;
1853                            }
1854                        }
1855                    }
1856                    self.env.pop_scope();
1857                    if !failed {
1858                        let msg = format!("Test '{}' passed", name);
1859                        self.output.push(msg.clone());
1860                        println!("{msg}");
1861                    }
1862                }
1863                Ok(Signal::None)
1864            }
1865            StmtKind::Use { item, .. } => self.exec_use(item),
1866            StmtKind::ModDecl { .. } => {
1867                // ModDecl is handled at module load time
1868                Ok(Signal::None)
1869            }
1870            StmtKind::TraitDef { .. } => {
1871                // Trait definitions are type-checker only; no runtime effect
1872                Ok(Signal::None)
1873            }
1874            StmtKind::TraitImpl {
1875                type_name, methods, ..
1876            } => {
1877                // Execute as a regular impl block — trait impls are type-erased at runtime
1878                let mut method_map = self.method_table.remove(type_name).unwrap_or_default();
1879                for method in methods {
1880                    if let StmtKind::FnDecl {
1881                        name,
1882                        params,
1883                        body,
1884                        is_generator,
1885                        ..
1886                    } = &method.kind
1887                    {
1888                        method_map.insert(
1889                            name.clone(),
1890                            Value::Function {
1891                                name: name.clone(),
1892                                params: params.clone(),
1893                                body: body.clone(),
1894                                is_generator: *is_generator,
1895                            },
1896                        );
1897                    }
1898                }
1899                self.method_table.insert(type_name.clone(), method_map);
1900                Ok(Signal::None)
1901            }
1902            StmtKind::LetDestructure { pattern, value, .. } => {
1903                let val = self.eval_expr(value)?;
1904                let bindings = self.match_pattern(pattern, &val).unwrap_or_default();
1905                for (name, bval) in bindings {
1906                    self.env.set(name, bval);
1907                }
1908                Ok(Signal::None)
1909            }
1910            StmtKind::TypeAlias { .. } => {
1911                // Type aliases are type-checker only; no runtime effect
1912                Ok(Signal::None)
1913            }
1914            StmtKind::Migrate {
1915                schema_name,
1916                from_version,
1917                to_version,
1918                operations,
1919            } => self.exec_migrate(schema_name, *from_version, *to_version, operations),
1920            StmtKind::Break => Ok(Signal::Break),
1921            StmtKind::Continue => Ok(Signal::Continue),
1922        }
1923    }
1924
1925    /// Execute a migrate block — apply migration operations to schema registry
1926    fn exec_migrate(
1927        &mut self,
1928        schema_name: &str,
1929        from_version: i64,
1930        to_version: i64,
1931        operations: &[MigrateOp],
1932    ) -> Result<Signal, TlError> {
1933        let ops: Vec<tl_compiler::schema::MigrationOp> = operations
1934            .iter()
1935            .map(|op| {
1936                match op {
1937                    MigrateOp::AddColumn {
1938                        name,
1939                        type_ann,
1940                        default,
1941                    } => tl_compiler::schema::MigrationOp::AddColumn {
1942                        name: name.clone(),
1943                        type_name: format!("{:?}", type_ann),
1944                        default: default.as_ref().map(|d| format!("{:?}", d)),
1945                    },
1946                    MigrateOp::DropColumn { name } => {
1947                        tl_compiler::schema::MigrationOp::DropColumn { name: name.clone() }
1948                    }
1949                    MigrateOp::RenameColumn { from, to } => {
1950                        tl_compiler::schema::MigrationOp::RenameColumn {
1951                            from: from.clone(),
1952                            to: to.clone(),
1953                        }
1954                    }
1955                    MigrateOp::AlterType { column, new_type } => {
1956                        tl_compiler::schema::MigrationOp::AlterType {
1957                            column: column.clone(),
1958                            new_type: format!("{:?}", new_type),
1959                        }
1960                    }
1961                    MigrateOp::AddConstraint { .. } | MigrateOp::DropConstraint { .. } => {
1962                        // Constraints are metadata-only; no Arrow schema change
1963                        tl_compiler::schema::MigrationOp::AddColumn {
1964                            name: String::new(),
1965                            type_name: String::new(),
1966                            default: None,
1967                        }
1968                    }
1969                }
1970            })
1971            .collect();
1972
1973        self.schema_registry
1974            .apply_migration(schema_name, from_version, to_version, &ops)
1975            .map_err(|e| {
1976                TlError::Runtime(RuntimeError {
1977                    message: format!("Migration error: {}", e),
1978                    span: None,
1979                    stack_trace: vec![],
1980                })
1981            })?;
1982        Ok(Signal::None)
1983    }
1984
1985    /// Evaluate a closure body (either expr or block).
1986    fn eval_closure_body(&mut self, body: &ClosureBody) -> Result<Value, TlError> {
1987        match body {
1988            ClosureBody::Expr(e) => self.eval_expr(e),
1989            ClosureBody::Block { stmts, expr } => {
1990                for s in stmts {
1991                    match self.exec_stmt(s)? {
1992                        Signal::Return(val) => return Ok(val),
1993                        Signal::Throw(val) => {
1994                            return Err(TlError::Runtime(RuntimeError {
1995                                message: format!("{}", val),
1996                                span: None,
1997                                stack_trace: vec![],
1998                            }));
1999                        }
2000                        _ => {}
2001                    }
2002                }
2003                if let Some(e) = expr {
2004                    self.eval_expr(e)
2005                } else {
2006                    Ok(Value::None)
2007                }
2008            }
2009        }
2010    }
2011
2012    /// Match a pattern against a value. Returns Some(bindings) if matched, None if not.
2013    fn match_pattern(&self, pattern: &Pattern, value: &Value) -> Option<Vec<(String, Value)>> {
2014        match pattern {
2015            Pattern::Wildcard => Some(vec![]),
2016            Pattern::Binding(name) => Some(vec![(name.clone(), value.clone())]),
2017            Pattern::Literal(expr) => {
2018                // Compare the literal expression value against the subject
2019                let pat_val = match expr {
2020                    Expr::Int(n) => Value::Int(*n),
2021                    Expr::Float(n) => Value::Float(*n),
2022                    Expr::Decimal(s) => {
2023                        use std::str::FromStr;
2024                        let cleaned = s.trim_end_matches('d');
2025                        match rust_decimal::Decimal::from_str(cleaned) {
2026                            Ok(d) => Value::Decimal(d),
2027                            Err(_) => return None,
2028                        }
2029                    }
2030                    Expr::String(s) => Value::String(s.clone()),
2031                    Expr::Bool(b) => Value::Bool(*b),
2032                    Expr::None => Value::None,
2033                    _ => return None,
2034                };
2035                if values_equal(value, &pat_val) {
2036                    Some(vec![])
2037                } else {
2038                    None
2039                }
2040            }
2041            Pattern::Enum {
2042                type_name: _,
2043                variant,
2044                args,
2045            } => {
2046                if let Value::EnumInstance {
2047                    variant: sv,
2048                    fields,
2049                    ..
2050                } = value
2051                    && variant == sv
2052                {
2053                    let mut bindings = vec![];
2054                    for (i, arg_pat) in args.iter().enumerate() {
2055                        let field_val = fields.get(i).cloned().unwrap_or(Value::None);
2056                        match self.match_pattern(arg_pat, &field_val) {
2057                            Some(sub_bindings) => bindings.extend(sub_bindings),
2058                            None => return None,
2059                        }
2060                    }
2061                    return Some(bindings);
2062                }
2063                None
2064            }
2065            Pattern::Struct {
2066                name: struct_name,
2067                fields,
2068            } => {
2069                // Check it's a struct instance
2070                if let Value::StructInstance {
2071                    type_name,
2072                    fields: sfields,
2073                } = value
2074                {
2075                    if let Some(expected) = struct_name
2076                        && expected != type_name
2077                    {
2078                        return None;
2079                    }
2080                    let mut bindings = vec![];
2081                    for field in fields {
2082                        let field_val = sfields.get(&field.name).cloned().unwrap_or(Value::None);
2083                        match &field.pattern {
2084                            None => {
2085                                // Shorthand: { x } binds x
2086                                bindings.push((field.name.clone(), field_val));
2087                            }
2088                            Some(sub_pat) => match self.match_pattern(sub_pat, &field_val) {
2089                                Some(sub_bindings) => bindings.extend(sub_bindings),
2090                                None => return None,
2091                            },
2092                        }
2093                    }
2094                    return Some(bindings);
2095                }
2096                None
2097            }
2098            Pattern::List { elements, rest } => {
2099                if let Value::List(items) = value {
2100                    if rest.is_some() {
2101                        if items.len() < elements.len() {
2102                            return None;
2103                        }
2104                    } else if items.len() != elements.len() {
2105                        return None;
2106                    }
2107                    let mut bindings = vec![];
2108                    for (i, elem_pat) in elements.iter().enumerate() {
2109                        let item_val = items.get(i).cloned().unwrap_or(Value::None);
2110                        match self.match_pattern(elem_pat, &item_val) {
2111                            Some(sub_bindings) => bindings.extend(sub_bindings),
2112                            None => return None,
2113                        }
2114                    }
2115                    if let Some(rest_name) = rest {
2116                        let rest_items = items[elements.len()..].to_vec();
2117                        bindings.push((rest_name.clone(), Value::List(rest_items)));
2118                    }
2119                    return Some(bindings);
2120                }
2121                None
2122            }
2123            Pattern::Or(patterns) => {
2124                for sub_pat in patterns {
2125                    if let Some(bindings) = self.match_pattern(sub_pat, value) {
2126                        return Some(bindings);
2127                    }
2128                }
2129                None
2130            }
2131        }
2132    }
2133
2134    fn exec_block(&mut self, stmts: &[Stmt]) -> Result<Signal, TlError> {
2135        self.env.push_scope();
2136        let mut result = Signal::None;
2137        for stmt in stmts {
2138            result = self.exec_stmt(stmt)?;
2139            match &result {
2140                Signal::Return(_)
2141                | Signal::Break
2142                | Signal::Continue
2143                | Signal::Throw(_)
2144                | Signal::Yield(_) => {
2145                    self.env.pop_scope();
2146                    return Ok(result);
2147                }
2148                Signal::None => {}
2149            }
2150        }
2151        self.env.pop_scope();
2152        Ok(result)
2153    }
2154
2155    // ── Expression evaluation ────────────────────────────────
2156
2157    fn eval_expr(&mut self, expr: &Expr) -> Result<Value, TlError> {
2158        match expr {
2159            Expr::Int(n) => Ok(Value::Int(*n)),
2160            Expr::Float(n) => Ok(Value::Float(*n)),
2161            Expr::Decimal(s) => {
2162                use std::str::FromStr;
2163                let cleaned = s.trim_end_matches('d');
2164                let d = rust_decimal::Decimal::from_str(cleaned)
2165                    .map_err(|e| runtime_err(format!("Invalid decimal: {e}")))?;
2166                Ok(Value::Decimal(d))
2167            }
2168            Expr::String(s) => Ok(Value::String(self.interpolate_string(s)?)),
2169            Expr::Bool(b) => Ok(Value::Bool(*b)),
2170            Expr::None => Ok(Value::None),
2171
2172            Expr::Ident(name) => {
2173                let val = self.env.get(name).cloned().ok_or_else(|| {
2174                    TlError::Runtime(RuntimeError {
2175                        message: format!("Undefined variable: `{name}`"),
2176                        span: None,
2177                        stack_trace: vec![],
2178                    })
2179                })?;
2180                if matches!(val, Value::Moved) {
2181                    return Err(runtime_err(format!(
2182                        "Use of moved value `{name}`. It was consumed by a pipe (|>) operation. Use .clone() to keep a copy."
2183                    )));
2184                }
2185                Ok(val)
2186            }
2187
2188            Expr::BinOp { left, op, right } => {
2189                let l = self.eval_expr(left)?;
2190                let r = self.eval_expr(right)?;
2191                self.eval_binop(&l, op, &r)
2192            }
2193
2194            Expr::UnaryOp { op, expr } => {
2195                let val = self.eval_expr(expr)?;
2196                match op {
2197                    UnaryOp::Neg => match val {
2198                        Value::Int(n) => Ok(Value::Int(-n)),
2199                        Value::Float(n) => Ok(Value::Float(-n)),
2200                        Value::Decimal(d) => Ok(Value::Decimal(-d)),
2201                        _ => Err(runtime_err(format!("Cannot negate {}", val.type_name()))),
2202                    },
2203                    UnaryOp::Not => Ok(Value::Bool(!val.is_truthy())),
2204                    UnaryOp::Ref => Ok(Value::Ref(Arc::new(val))),
2205                }
2206            }
2207
2208            Expr::Call { function, args } => {
2209                // Method call: obj.method(args) — where function is Member { object, field }
2210                if let Expr::Member { object, field } = function.as_ref() {
2211                    let obj = self.eval_expr(object)?;
2212                    let mut eval_args = Vec::new();
2213                    for arg in args {
2214                        eval_args.push(self.eval_expr(arg)?);
2215                    }
2216                    return self.call_method(&obj, field, &eval_args);
2217                }
2218                let func = self.eval_expr(function)?;
2219                let mut eval_args = Vec::new();
2220                for arg in args {
2221                    eval_args.push(self.eval_expr(arg)?);
2222                }
2223                self.call_function(&func, &eval_args)
2224            }
2225
2226            Expr::Member { object, field } => {
2227                let obj = self.eval_expr(object)?;
2228                match &obj {
2229                    Value::StructInstance { fields, .. } => fields
2230                        .get(field)
2231                        .cloned()
2232                        .ok_or_else(|| runtime_err(format!("Struct has no field `{field}`"))),
2233                    Value::Module { exports, name } => {
2234                        exports.get(field).cloned().ok_or_else(|| {
2235                            runtime_err(format!("Module `{name}` has no export `{field}`"))
2236                        })
2237                    }
2238                    Value::Map(pairs) => Ok(pairs
2239                        .iter()
2240                        .find(|(k, _)| k == field)
2241                        .map(|(_, v)| v.clone())
2242                        .unwrap_or(Value::None)),
2243                    #[cfg(feature = "python")]
2244                    Value::PyObject(wrapper) => Ok(interp_py_get_member(wrapper, field)),
2245                    _ => Err(runtime_err(format!(
2246                        "Cannot access field `{field}` on {}",
2247                        obj.type_name()
2248                    ))),
2249                }
2250            }
2251
2252            Expr::Pipe { left, right } => {
2253                let left_val = self.eval_expr(left)?;
2254                // Table-aware pipe: if left is a Table, dispatch to table operations
2255                if let Value::Table(ref tl_table) = left_val {
2256                    return self.eval_table_pipe(tl_table.df.clone(), right);
2257                }
2258                // Mark the source variable as moved (consumed by pipe)
2259                if let Expr::Ident(name) = left.as_ref() {
2260                    self.env.set(name.clone(), Value::Moved);
2261                }
2262                // Regular pipe: left_val becomes the first argument to the right-side call
2263                match right.as_ref() {
2264                    Expr::Call { function, args } => {
2265                        let func = self.eval_expr(function)?;
2266                        let mut all_args = vec![left_val];
2267                        for arg in args {
2268                            all_args.push(self.eval_expr(arg)?);
2269                        }
2270                        self.call_function(&func, &all_args)
2271                    }
2272                    Expr::Ident(name) => {
2273                        let func = self.env.get(name).cloned().ok_or_else(|| {
2274                            TlError::Runtime(RuntimeError {
2275                                message: format!("Undefined function: `{name}`"),
2276                                span: None,
2277                                stack_trace: vec![],
2278                            })
2279                        })?;
2280                        self.call_function(&func, &[left_val])
2281                    }
2282                    _ => Err(runtime_err(
2283                        "Right side of |> must be a function call".to_string(),
2284                    )),
2285                }
2286            }
2287
2288            Expr::List(elements) => {
2289                let mut items = Vec::new();
2290                for el in elements {
2291                    items.push(self.eval_expr(el)?);
2292                }
2293                Ok(Value::List(items))
2294            }
2295
2296            Expr::Index { object, index } => {
2297                let obj = self.eval_expr(object)?;
2298                let idx = self.eval_expr(index)?;
2299                match (&obj, &idx) {
2300                    (Value::List(items), Value::Int(i)) => {
2301                        let idx = if *i < 0 {
2302                            let adjusted = items.len() as i64 + *i;
2303                            if adjusted < 0 {
2304                                return Err(runtime_err(format!(
2305                                    "Index {} out of bounds for list of length {}",
2306                                    i,
2307                                    items.len()
2308                                )));
2309                            }
2310                            adjusted as usize
2311                        } else {
2312                            *i as usize
2313                        };
2314                        items.get(idx).cloned().ok_or_else(|| {
2315                            runtime_err(format!(
2316                                "Index {} out of bounds for list of length {}",
2317                                i,
2318                                items.len()
2319                            ))
2320                        })
2321                    }
2322                    (Value::Map(pairs), Value::String(key)) => Ok(pairs
2323                        .iter()
2324                        .find(|(k, _)| k == key)
2325                        .map(|(_, v)| v.clone())
2326                        .unwrap_or(Value::None)),
2327                    _ => Err(runtime_err(format!(
2328                        "Cannot index {} with {}",
2329                        obj.type_name(),
2330                        idx.type_name()
2331                    ))),
2332                }
2333            }
2334
2335            Expr::Case { arms } => {
2336                for arm in arms {
2337                    match &arm.pattern {
2338                        Pattern::Wildcard | Pattern::Binding(_) => {
2339                            return self.eval_expr(&arm.body);
2340                        }
2341                        Pattern::Literal(expr) => {
2342                            let val = self.eval_expr(expr)?;
2343                            if val.is_truthy() {
2344                                return self.eval_expr(&arm.body);
2345                            }
2346                        }
2347                        _ => {}
2348                    }
2349                }
2350                Ok(Value::None)
2351            }
2352
2353            Expr::Match { subject, arms } => {
2354                let subject_val = self.eval_expr(subject)?;
2355                for arm in arms {
2356                    if let Some(bindings) = self.match_pattern(&arm.pattern, &subject_val) {
2357                        self.env.push_scope();
2358                        for (name, val) in &bindings {
2359                            self.env.set(name.clone(), val.clone());
2360                        }
2361                        // Check guard
2362                        if let Some(guard) = &arm.guard {
2363                            let guard_val = self.eval_expr(guard)?;
2364                            if !guard_val.is_truthy() {
2365                                self.env.pop_scope();
2366                                continue;
2367                            }
2368                        }
2369                        let result = self.eval_expr(&arm.body);
2370                        self.env.pop_scope();
2371                        return result;
2372                    }
2373                }
2374                Ok(Value::None)
2375            }
2376
2377            Expr::NullCoalesce { expr, default } => {
2378                let val = self.eval_expr(expr)?;
2379                if matches!(val, Value::None) {
2380                    self.eval_expr(default)
2381                } else {
2382                    Ok(val)
2383                }
2384            }
2385
2386            Expr::Closure { params, body, .. } => Ok(Value::Closure {
2387                params: params.clone(),
2388                body: body.clone(),
2389                captured_env: self.env.scopes.clone(),
2390            }),
2391
2392            Expr::Assign { target, value } => {
2393                let val = self.eval_expr(value)?;
2394                match target.as_ref() {
2395                    Expr::Ident(name) => {
2396                        if self.env.update(name, val.clone()) {
2397                            Ok(val)
2398                        } else {
2399                            Err(runtime_err(format!("Undefined variable: `{name}`")))
2400                        }
2401                    }
2402                    Expr::Member { object, field } => {
2403                        // Struct field assignment: s.x = val
2404                        if let Expr::Ident(name) = object.as_ref() {
2405                            let obj = self.env.get(name).cloned();
2406                            match obj {
2407                                Some(Value::StructInstance {
2408                                    type_name,
2409                                    mut fields,
2410                                }) => {
2411                                    fields.insert(field.clone(), val.clone());
2412                                    self.env
2413                                        .update(name, Value::StructInstance { type_name, fields });
2414                                    Ok(val)
2415                                }
2416                                Some(Value::Map(mut pairs)) => {
2417                                    if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == field)
2418                                    {
2419                                        entry.1 = val.clone();
2420                                    } else {
2421                                        pairs.push((field.clone(), val.clone()));
2422                                    }
2423                                    self.env.update(name, Value::Map(pairs));
2424                                    Ok(val)
2425                                }
2426                                _ => Err(runtime_err(format!("Cannot set field on {}", name))),
2427                            }
2428                        } else {
2429                            Err(runtime_err("Invalid assignment target".to_string()))
2430                        }
2431                    }
2432                    Expr::Index { object, index } => {
2433                        // Map/list index assignment: m["key"] = val, list[0] = val
2434                        if let Expr::Ident(name) = object.as_ref() {
2435                            let idx = self.eval_expr(index)?;
2436                            let obj = self.env.get(name).cloned();
2437                            match (obj, idx) {
2438                                (Some(Value::Map(mut pairs)), Value::String(key)) => {
2439                                    if let Some(entry) = pairs.iter_mut().find(|(k, _)| k == &key) {
2440                                        entry.1 = val.clone();
2441                                    } else {
2442                                        pairs.push((key, val.clone()));
2443                                    }
2444                                    self.env.update(name, Value::Map(pairs));
2445                                    Ok(val)
2446                                }
2447                                (Some(Value::List(mut items)), Value::Int(i)) => {
2448                                    let idx = if i < 0 {
2449                                        let adjusted = items.len() as i64 + i;
2450                                        if adjusted < 0 {
2451                                            return Err(runtime_err(format!(
2452                                                "Index {} out of bounds for list of length {}",
2453                                                i,
2454                                                items.len()
2455                                            )));
2456                                        }
2457                                        adjusted as usize
2458                                    } else {
2459                                        i as usize
2460                                    };
2461                                    if idx < items.len() {
2462                                        items[idx] = val.clone();
2463                                        self.env.update(name, Value::List(items));
2464                                        Ok(val)
2465                                    } else {
2466                                        Err(runtime_err(format!(
2467                                            "Index {} out of bounds for list of length {}",
2468                                            i,
2469                                            items.len()
2470                                        )))
2471                                    }
2472                                }
2473                                _ => {
2474                                    Err(runtime_err("Invalid index assignment target".to_string()))
2475                                }
2476                            }
2477                        } else {
2478                            Err(runtime_err("Invalid assignment target".to_string()))
2479                        }
2480                    }
2481                    _ => Err(runtime_err("Invalid assignment target".to_string())),
2482                }
2483            }
2484
2485            Expr::StructInit { name, fields } => {
2486                let def = self.env.get(name).cloned();
2487                match def {
2488                    Some(Value::StructDef {
2489                        name: type_name,
2490                        fields: def_fields,
2491                    }) => {
2492                        let mut field_map = HashMap::new();
2493                        for (fname, fexpr) in fields {
2494                            let fval = self.eval_expr(fexpr)?;
2495                            if !def_fields.contains(fname) {
2496                                return Err(runtime_err(format!(
2497                                    "Unknown field `{fname}` on struct `{type_name}`"
2498                                )));
2499                            }
2500                            field_map.insert(fname.clone(), fval);
2501                        }
2502                        Ok(Value::StructInstance {
2503                            type_name,
2504                            fields: field_map,
2505                        })
2506                    }
2507                    _ => Err(runtime_err(format!("Unknown struct type: `{name}`"))),
2508                }
2509            }
2510
2511            Expr::EnumVariant {
2512                enum_name,
2513                variant,
2514                args,
2515            } => {
2516                let def = self.env.get(enum_name).cloned();
2517                match def {
2518                    Some(Value::EnumDef {
2519                        name: type_name,
2520                        variants,
2521                    }) => {
2522                        if let Some((_, expected_count)) =
2523                            variants.iter().find(|(v, _)| v == variant)
2524                        {
2525                            let mut eval_args = Vec::new();
2526                            for arg in args {
2527                                eval_args.push(self.eval_expr(arg)?);
2528                            }
2529                            if eval_args.len() != *expected_count {
2530                                return Err(runtime_err(format!(
2531                                    "Enum variant {}::{} expects {} arguments, got {}",
2532                                    type_name,
2533                                    variant,
2534                                    expected_count,
2535                                    eval_args.len()
2536                                )));
2537                            }
2538                            Ok(Value::EnumInstance {
2539                                type_name,
2540                                variant: variant.clone(),
2541                                fields: eval_args,
2542                            })
2543                        } else {
2544                            Err(runtime_err(format!(
2545                                "Unknown variant `{variant}` on enum `{type_name}`"
2546                            )))
2547                        }
2548                    }
2549                    _ => Err(runtime_err(format!("Unknown enum type: `{enum_name}`"))),
2550                }
2551            }
2552
2553            Expr::Yield(_) => {
2554                // Yield should not be directly evaluated in interpreter — it's handled
2555                // by the generator thread infrastructure. If we get here, yield was used
2556                // outside a generator context.
2557                Err(runtime_err(
2558                    "yield used outside of a generator function".to_string(),
2559                ))
2560            }
2561            Expr::Await(inner) => {
2562                let val = self.eval_expr(inner)?;
2563                match val {
2564                    Value::Task(task) => {
2565                        let rx = {
2566                            let mut guard = task.receiver.lock().unwrap_or_else(|e| e.into_inner());
2567                            guard.take()
2568                        };
2569                        match rx {
2570                            Some(receiver) => match receiver.recv() {
2571                                Ok(Ok(result)) => Ok(result),
2572                                Ok(Err(err_msg)) => Err(runtime_err(err_msg)),
2573                                Err(_) => Err(runtime_err("Task channel disconnected".to_string())),
2574                            },
2575                            None => Err(runtime_err("Task already awaited".to_string())),
2576                        }
2577                    }
2578                    // Non-task values pass through
2579                    other => Ok(other),
2580                }
2581            }
2582
2583            Expr::Try(inner) => {
2584                let val = self.eval_expr(inner)?;
2585                match val {
2586                    Value::EnumInstance {
2587                        ref type_name,
2588                        ref variant,
2589                        ref fields,
2590                    } if type_name == "Result" => {
2591                        if variant == "Ok" && !fields.is_empty() {
2592                            Ok(fields[0].clone())
2593                        } else if variant == "Err" {
2594                            // Propagate: signal early return with Err value
2595                            let err_msg = if fields.is_empty() {
2596                                "error".to_string()
2597                            } else {
2598                                format!("{}", fields[0])
2599                            };
2600                            Err(TlError::Runtime(tl_errors::RuntimeError {
2601                                message: format!("__try_propagate__:{err_msg}"),
2602                                span: None,
2603                                stack_trace: vec![],
2604                            }))
2605                        } else {
2606                            Ok(val)
2607                        }
2608                    }
2609                    Value::None => {
2610                        // Propagate: early return None
2611                        Err(TlError::Runtime(tl_errors::RuntimeError {
2612                            message: "__try_propagate_none__".to_string(),
2613                            span: None,
2614                            stack_trace: vec![],
2615                        }))
2616                    }
2617                    _ => Ok(val), // passthrough
2618                }
2619            }
2620
2621            Expr::Map(pairs) => {
2622                let mut result = Vec::new();
2623                for (key_expr, val_expr) in pairs {
2624                    let key = match self.eval_expr(key_expr)? {
2625                        Value::String(s) => s,
2626                        other => format!("{other}"),
2627                    };
2628                    let val = self.eval_expr(val_expr)?;
2629                    result.push((key, val));
2630                }
2631                Ok(Value::Map(result))
2632            }
2633
2634            _ => Err(runtime_err(format!("Unsupported expression: {expr:?}"))),
2635        }
2636    }
2637
2638    fn eval_binop(&self, left: &Value, op: &BinOp, right: &Value) -> Result<Value, TlError> {
2639        match (left, right) {
2640            // Int operations
2641            (Value::Int(a), Value::Int(b)) => match op {
2642                BinOp::Add => Ok(a
2643                    .checked_add(*b)
2644                    .map(Value::Int)
2645                    .unwrap_or_else(|| Value::Float(*a as f64 + *b as f64))),
2646                BinOp::Sub => Ok(a
2647                    .checked_sub(*b)
2648                    .map(Value::Int)
2649                    .unwrap_or_else(|| Value::Float(*a as f64 - *b as f64))),
2650                BinOp::Mul => Ok(a
2651                    .checked_mul(*b)
2652                    .map(Value::Int)
2653                    .unwrap_or_else(|| Value::Float(*a as f64 * *b as f64))),
2654                BinOp::Div => {
2655                    if *b == 0 {
2656                        Err(runtime_err("Division by zero".to_string()))
2657                    } else {
2658                        Ok(Value::Int(a / b))
2659                    }
2660                }
2661                BinOp::Mod => {
2662                    if *b == 0 {
2663                        Err(runtime_err("Modulo by zero".to_string()))
2664                    } else {
2665                        Ok(Value::Int(a % b))
2666                    }
2667                }
2668                BinOp::Pow => {
2669                    if *b < 0 {
2670                        Ok(Value::Float((*a as f64).powi(*b as i32)))
2671                    } else {
2672                        match a.checked_pow(*b as u32) {
2673                            Some(result) => Ok(Value::Int(result)),
2674                            None => Ok(Value::Float((*a as f64).powf(*b as f64))),
2675                        }
2676                    }
2677                }
2678                BinOp::Eq => Ok(Value::Bool(a == b)),
2679                BinOp::Neq => Ok(Value::Bool(a != b)),
2680                BinOp::Lt => Ok(Value::Bool(a < b)),
2681                BinOp::Gt => Ok(Value::Bool(a > b)),
2682                BinOp::Lte => Ok(Value::Bool(a <= b)),
2683                BinOp::Gte => Ok(Value::Bool(a >= b)),
2684                BinOp::And => Ok(Value::Bool(*a != 0 && *b != 0)),
2685                BinOp::Or => Ok(Value::Bool(*a != 0 || *b != 0)),
2686            },
2687
2688            // Float operations
2689            (Value::Float(a), Value::Float(b)) => match op {
2690                BinOp::Add => Ok(Value::Float(a + b)),
2691                BinOp::Sub => Ok(Value::Float(a - b)),
2692                BinOp::Mul => Ok(Value::Float(a * b)),
2693                BinOp::Div => {
2694                    if *b == 0.0 {
2695                        Err(runtime_err("Division by zero".to_string()))
2696                    } else {
2697                        Ok(Value::Float(a / b))
2698                    }
2699                }
2700                BinOp::Mod => {
2701                    if *b == 0.0 {
2702                        Err(runtime_err("Modulo by zero".to_string()))
2703                    } else {
2704                        Ok(Value::Float(a % b))
2705                    }
2706                }
2707                BinOp::Pow => Ok(Value::Float(a.powf(*b))),
2708                BinOp::Eq => Ok(Value::Bool(a == b)),
2709                BinOp::Neq => Ok(Value::Bool(a != b)),
2710                BinOp::Lt => Ok(Value::Bool(a < b)),
2711                BinOp::Gt => Ok(Value::Bool(a > b)),
2712                BinOp::Lte => Ok(Value::Bool(a <= b)),
2713                BinOp::Gte => Ok(Value::Bool(a >= b)),
2714                _ => Err(runtime_err(format!("Unsupported op: float {op} float"))),
2715            },
2716
2717            // Int-Float mixed (promote int to float)
2718            (Value::Int(a), Value::Float(b)) => {
2719                self.eval_binop(&Value::Float(*a as f64), op, &Value::Float(*b))
2720            }
2721            (Value::Float(a), Value::Int(b)) => {
2722                self.eval_binop(&Value::Float(*a), op, &Value::Float(*b as f64))
2723            }
2724
2725            // String concatenation
2726            (Value::String(a), Value::String(b)) if *op == BinOp::Add => {
2727                Ok(Value::String(format!("{a}{b}")))
2728            }
2729
2730            // String repeat
2731            (Value::String(a), Value::Int(b)) if *op == BinOp::Mul => {
2732                if *b < 0 {
2733                    return Err(runtime_err(
2734                        "Cannot repeat string a negative number of times".to_string(),
2735                    ));
2736                }
2737                if *b > 10_000_000 {
2738                    return Err(runtime_err(
2739                        "String repeat count too large (max 10,000,000)".to_string(),
2740                    ));
2741                }
2742                Ok(Value::String(a.repeat(*b as usize)))
2743            }
2744
2745            // Boolean logic
2746            (Value::Bool(a), Value::Bool(b)) => match op {
2747                BinOp::And => Ok(Value::Bool(*a && *b)),
2748                BinOp::Or => Ok(Value::Bool(*a || *b)),
2749                BinOp::Eq => Ok(Value::Bool(a == b)),
2750                BinOp::Neq => Ok(Value::Bool(a != b)),
2751                _ => Err(runtime_err(format!("Unsupported op: bool {op} bool"))),
2752            },
2753
2754            // String equality
2755            (Value::String(a), Value::String(b)) => match op {
2756                BinOp::Eq => Ok(Value::Bool(a == b)),
2757                BinOp::Neq => Ok(Value::Bool(a != b)),
2758                _ => Err(runtime_err(format!("Unsupported op: string {op} string"))),
2759            },
2760
2761            // Tensor arithmetic
2762            (Value::Tensor(a), Value::Tensor(b)) => match op {
2763                BinOp::Add => {
2764                    let result = a.add(b).map_err(runtime_err)?;
2765                    Ok(Value::Tensor(result))
2766                }
2767                BinOp::Sub => {
2768                    let result = a.sub(b).map_err(runtime_err)?;
2769                    Ok(Value::Tensor(result))
2770                }
2771                BinOp::Mul => {
2772                    let result = a.mul(b).map_err(runtime_err)?;
2773                    Ok(Value::Tensor(result))
2774                }
2775                BinOp::Div => {
2776                    let result = a.div(b).map_err(runtime_err)?;
2777                    Ok(Value::Tensor(result))
2778                }
2779                _ => Err(runtime_err(format!("Unsupported op: tensor {op} tensor"))),
2780            },
2781
2782            // Tensor * scalar
2783            (Value::Tensor(t), Value::Float(s)) | (Value::Float(s), Value::Tensor(t))
2784                if *op == BinOp::Mul =>
2785            {
2786                Ok(Value::Tensor(t.scale(*s)))
2787            }
2788
2789            // Decimal arithmetic (Phase 22)
2790            (Value::Decimal(a), Value::Decimal(b)) => match op {
2791                BinOp::Add => Ok(Value::Decimal(a + b)),
2792                BinOp::Sub => Ok(Value::Decimal(a - b)),
2793                BinOp::Mul => Ok(Value::Decimal(a * b)),
2794                BinOp::Div => {
2795                    if b.is_zero() {
2796                        Err(runtime_err("Division by zero".to_string()))
2797                    } else {
2798                        Ok(Value::Decimal(a / b))
2799                    }
2800                }
2801                BinOp::Eq => Ok(Value::Bool(a == b)),
2802                BinOp::Neq => Ok(Value::Bool(a != b)),
2803                BinOp::Lt => Ok(Value::Bool(a < b)),
2804                BinOp::Gt => Ok(Value::Bool(a > b)),
2805                BinOp::Lte => Ok(Value::Bool(a <= b)),
2806                BinOp::Gte => Ok(Value::Bool(a >= b)),
2807                _ => Err(runtime_err(format!("Unsupported op: decimal {op} decimal"))),
2808            },
2809            // Decimal + Int -> Decimal
2810            (Value::Decimal(a), Value::Int(b)) => {
2811                let b_dec = rust_decimal::Decimal::from(*b);
2812                self.eval_binop(&Value::Decimal(*a), op, &Value::Decimal(b_dec))
2813            }
2814            (Value::Int(a), Value::Decimal(b)) => {
2815                let a_dec = rust_decimal::Decimal::from(*a);
2816                self.eval_binop(&Value::Decimal(a_dec), op, &Value::Decimal(*b))
2817            }
2818            // Decimal + Float -> Float
2819            (Value::Decimal(a), Value::Float(b)) => {
2820                use rust_decimal::prelude::ToPrimitive;
2821                let a_f = a.to_f64().unwrap_or(0.0);
2822                self.eval_binop(&Value::Float(a_f), op, &Value::Float(*b))
2823            }
2824            (Value::Float(a), Value::Decimal(b)) => {
2825                use rust_decimal::prelude::ToPrimitive;
2826                let b_f = b.to_f64().unwrap_or(0.0);
2827                self.eval_binop(&Value::Float(*a), op, &Value::Float(b_f))
2828            }
2829
2830            _ => Err(runtime_err(format!(
2831                "Cannot apply `{op}` to {} and {}",
2832                left.type_name(),
2833                right.type_name()
2834            ))),
2835        }
2836    }
2837
2838    fn call_function(&mut self, func: &Value, args: &[Value]) -> Result<Value, TlError> {
2839        match func {
2840            Value::Builtin(name) => self.call_builtin(name, args),
2841            Value::Function {
2842                params,
2843                body,
2844                is_generator,
2845                ..
2846            } => {
2847                if args.len() != params.len() {
2848                    return Err(runtime_err(format!(
2849                        "Expected {} arguments, got {}",
2850                        params.len(),
2851                        args.len()
2852                    )));
2853                }
2854
2855                // If this is a generator function, create a generator coroutine
2856                if *is_generator {
2857                    return self.create_generator(params, body, args);
2858                }
2859
2860                self.env.push_scope();
2861                for (param, arg) in params.iter().zip(args) {
2862                    self.env.set(param.name.clone(), arg.clone());
2863                }
2864                let mut result = Value::None;
2865                for stmt in body {
2866                    match self.exec_stmt(stmt) {
2867                        Ok(Signal::Return(val)) => {
2868                            result = val;
2869                            break;
2870                        }
2871                        Ok(Signal::None) => {
2872                            if let Some(val) = &self.last_expr_value {
2873                                result = val.clone();
2874                            }
2875                        }
2876                        Err(TlError::Runtime(ref e))
2877                            if e.message.starts_with("__try_propagate__:") =>
2878                        {
2879                            // ? operator hit an Err — return the Err as this function's return value
2880                            let err_msg = e
2881                                .message
2882                                .strip_prefix("__try_propagate__:")
2883                                .unwrap_or("error");
2884                            self.env.pop_scope();
2885                            return Ok(Value::EnumInstance {
2886                                type_name: "Result".to_string(),
2887                                variant: "Err".to_string(),
2888                                fields: vec![Value::String(err_msg.to_string())],
2889                            });
2890                        }
2891                        Err(TlError::Runtime(ref e)) if e.message == "__try_propagate_none__" => {
2892                            // ? operator hit None — return None as this function's return value
2893                            self.env.pop_scope();
2894                            return Ok(Value::None);
2895                        }
2896                        Err(e) => {
2897                            self.env.pop_scope();
2898                            return Err(e);
2899                        }
2900                        Ok(_) => {}
2901                    }
2902                }
2903                self.env.pop_scope();
2904                Ok(result)
2905            }
2906            Value::Closure {
2907                params,
2908                body,
2909                captured_env,
2910            } => {
2911                if args.len() != params.len() {
2912                    return Err(runtime_err(format!(
2913                        "Closure expected {} arguments, got {}",
2914                        params.len(),
2915                        args.len()
2916                    )));
2917                }
2918                // Save current env, swap in captured env
2919                let saved_env = std::mem::replace(&mut self.env.scopes, captured_env.clone());
2920                self.env.push_scope();
2921                for (param, arg) in params.iter().zip(args) {
2922                    self.env.set(param.name.clone(), arg.clone());
2923                }
2924                let result = match &body {
2925                    ClosureBody::Expr(e) => self.eval_expr(e),
2926                    ClosureBody::Block { stmts, expr } => {
2927                        let mut early_return = None;
2928                        for s in stmts {
2929                            match self.exec_stmt(s) {
2930                                Ok(Signal::Return(val)) => {
2931                                    early_return = Some(val);
2932                                    break;
2933                                }
2934                                Ok(_) => {}
2935                                Err(e) => {
2936                                    self.env.scopes = saved_env;
2937                                    return Err(e);
2938                                }
2939                            }
2940                        }
2941                        if let Some(val) = early_return {
2942                            Ok(val)
2943                        } else if let Some(e) = expr {
2944                            self.eval_expr(e)
2945                        } else {
2946                            Ok(Value::None)
2947                        }
2948                    }
2949                };
2950                // Restore original env
2951                self.env.scopes = saved_env;
2952                result
2953            }
2954            _ => Err(runtime_err(format!("Cannot call {}", func.type_name()))),
2955        }
2956    }
2957
2958    /// Create a generator from a function with yield.
2959    /// Spawns a thread that executes the function body, pausing at each yield.
2960    fn create_generator(
2961        &mut self,
2962        params: &[Param],
2963        body: &[Stmt],
2964        args: &[Value],
2965    ) -> Result<Value, TlError> {
2966        let params = params.to_vec();
2967        let body = body.to_vec();
2968        let args = args.to_vec();
2969        let env_scopes = self.env.scopes.clone();
2970        let method_table = self.method_table.clone();
2971
2972        // Channel for yielded values: generator thread → consumer
2973        let (yield_tx, yield_rx) = mpsc::channel::<Result<Value, String>>();
2974        // Channel for resume signals: consumer → generator thread
2975        let (resume_tx, resume_rx) = mpsc::sync_channel::<()>(0);
2976
2977        std::thread::spawn(move || {
2978            let mut interp = Interpreter::new();
2979            interp.env.scopes = env_scopes;
2980            interp.method_table = method_table;
2981
2982            // Override yield behavior: instead of Expr::Yield erroring,
2983            // we handle Signal::Yield from exec_stmt
2984            interp.env.push_scope();
2985            for (param, arg) in params.iter().zip(&args) {
2986                interp.env.set(param.name.clone(), arg.clone());
2987            }
2988
2989            // Wait for first next() call before starting
2990            if resume_rx.recv().is_err() {
2991                return; // Consumer dropped — generator abandoned
2992            }
2993
2994            for stmt in &body {
2995                match interp.exec_stmt_gen(stmt, &yield_tx, &resume_rx) {
2996                    Ok(GenSignal::None) => {}
2997                    Ok(GenSignal::Return(_)) => break,
2998                    Ok(GenSignal::Break) | Ok(GenSignal::Continue) => {}
2999                    Ok(GenSignal::Yield(_)) => {
3000                        // Already handled inside exec_stmt_gen
3001                    }
3002                    Ok(GenSignal::Throw(v)) => {
3003                        let _ = yield_tx.send(Err(format!("{v}")));
3004                        return;
3005                    }
3006                    Err(e) => {
3007                        let _ = yield_tx.send(Err(format!("{e}")));
3008                        return;
3009                    }
3010                }
3011            }
3012            interp.env.pop_scope();
3013            // Generator function completed — channel closes naturally
3014        });
3015
3016        let gn = TlGenerator::new(TlGeneratorKind::UserDefined {
3017            receiver: Mutex::new(Some(yield_rx)),
3018            resume_tx,
3019        });
3020        Ok(Value::Generator(Arc::new(gn)))
3021    }
3022
3023    /// Advance a generator by one step.
3024    fn interpreter_next(&mut self, gen_arc: &Arc<TlGenerator>) -> Result<Value, TlError> {
3025        let done = *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner());
3026        if done {
3027            return Ok(Value::None);
3028        }
3029
3030        match &gen_arc.kind {
3031            TlGeneratorKind::UserDefined {
3032                receiver,
3033                resume_tx,
3034            } => {
3035                // Signal the generator thread to continue
3036                if resume_tx.send(()).is_err() {
3037                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3038                    return Ok(Value::None);
3039                }
3040                // Wait for the next yielded value
3041                let rx_guard = receiver.lock().unwrap_or_else(|e| e.into_inner());
3042                if let Some(rx) = rx_guard.as_ref() {
3043                    match rx.recv() {
3044                        Ok(Ok(val)) => Ok(val),
3045                        Ok(Err(err_msg)) => {
3046                            *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3047                            Err(runtime_err(err_msg))
3048                        }
3049                        Err(_) => {
3050                            // Channel closed — generator exhausted
3051                            *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3052                            Ok(Value::None)
3053                        }
3054                    }
3055                } else {
3056                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3057                    Ok(Value::None)
3058                }
3059            }
3060            TlGeneratorKind::ListIter { items, index } => {
3061                let mut idx = index.lock().unwrap_or_else(|e| e.into_inner());
3062                if *idx < items.len() {
3063                    let val = items[*idx].clone();
3064                    *idx += 1;
3065                    Ok(val)
3066                } else {
3067                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3068                    Ok(Value::None)
3069                }
3070            }
3071            TlGeneratorKind::Take { source, remaining } => {
3072                let mut rem = remaining.lock().unwrap_or_else(|e| e.into_inner());
3073                if *rem == 0 {
3074                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3075                    return Ok(Value::None);
3076                }
3077                *rem -= 1;
3078                drop(rem);
3079                let val = self.interpreter_next(source)?;
3080                if matches!(val, Value::None) {
3081                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3082                }
3083                Ok(val)
3084            }
3085            TlGeneratorKind::Skip { source, remaining } => {
3086                let mut rem = remaining.lock().unwrap_or_else(|e| e.into_inner());
3087                let skip_n = *rem;
3088                *rem = 0;
3089                drop(rem);
3090                for _ in 0..skip_n {
3091                    let val = self.interpreter_next(source)?;
3092                    if matches!(val, Value::None) {
3093                        *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3094                        return Ok(Value::None);
3095                    }
3096                }
3097                let val = self.interpreter_next(source)?;
3098                if matches!(val, Value::None) {
3099                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3100                }
3101                Ok(val)
3102            }
3103            TlGeneratorKind::Map { source, func } => {
3104                let val = self.interpreter_next(source)?;
3105                if matches!(val, Value::None) {
3106                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3107                    return Ok(Value::None);
3108                }
3109                self.call_function(func, &[val])
3110            }
3111            TlGeneratorKind::Filter { source, func } => loop {
3112                let val = self.interpreter_next(source)?;
3113                if matches!(val, Value::None) {
3114                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3115                    return Ok(Value::None);
3116                }
3117                let test = self.call_function(func, std::slice::from_ref(&val))?;
3118                if test.is_truthy() {
3119                    return Ok(val);
3120                }
3121            },
3122            TlGeneratorKind::Chain {
3123                first,
3124                second,
3125                on_second,
3126            } => {
3127                let is_second = *on_second.lock().unwrap_or_else(|e| e.into_inner());
3128                if !is_second {
3129                    let val = self.interpreter_next(first)?;
3130                    if matches!(val, Value::None) {
3131                        *on_second.lock().unwrap_or_else(|e| e.into_inner()) = true;
3132                        return self.interpreter_next(second);
3133                    }
3134                    Ok(val)
3135                } else {
3136                    let val = self.interpreter_next(second)?;
3137                    if matches!(val, Value::None) {
3138                        *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3139                    }
3140                    Ok(val)
3141                }
3142            }
3143            TlGeneratorKind::Zip { first, second } => {
3144                let val1 = self.interpreter_next(first)?;
3145                let val2 = self.interpreter_next(second)?;
3146                if matches!(val1, Value::None) || matches!(val2, Value::None) {
3147                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3148                    return Ok(Value::None);
3149                }
3150                Ok(Value::List(vec![val1, val2]))
3151            }
3152            TlGeneratorKind::Enumerate { source, index } => {
3153                let mut idx = index.lock().unwrap_or_else(|e| e.into_inner());
3154                let cur_idx = *idx;
3155                *idx += 1;
3156                drop(idx);
3157                let val = self.interpreter_next(source)?;
3158                if matches!(val, Value::None) {
3159                    *gen_arc.done.lock().unwrap_or_else(|e| e.into_inner()) = true;
3160                    return Ok(Value::None);
3161                }
3162                Ok(Value::List(vec![Value::Int(cur_idx as i64), val]))
3163            }
3164        }
3165    }
3166
3167    fn call_builtin(&mut self, name: &str, args: &[Value]) -> Result<Value, TlError> {
3168        match name {
3169            "print" | "println" => {
3170                // If any arg is a table, auto-collect and display it
3171                let mut parts = Vec::new();
3172                for a in args {
3173                    match a {
3174                        Value::Table(t) => {
3175                            let batches =
3176                                self.engine().collect(t.df.clone()).map_err(runtime_err)?;
3177                            let formatted =
3178                                DataEngine::format_batches(&batches).map_err(runtime_err)?;
3179                            parts.push(formatted);
3180                        }
3181                        _ => parts.push(format!("{a}")),
3182                    }
3183                }
3184                let line = parts.join(" ");
3185                println!("{line}");
3186                self.output.push(line);
3187                Ok(Value::None)
3188            }
3189            "len" => match args.first() {
3190                Some(Value::String(s)) => Ok(Value::Int(s.len() as i64)),
3191                Some(Value::List(l)) => Ok(Value::Int(l.len() as i64)),
3192                Some(Value::Map(pairs)) => Ok(Value::Int(pairs.len() as i64)),
3193                Some(Value::Set(items)) => Ok(Value::Int(items.len() as i64)),
3194                _ => Err(runtime_err(
3195                    "len() expects a string, list, map, or set".to_string(),
3196                )),
3197            },
3198            "str" => Ok(Value::String(
3199                args.first().map(|v| format!("{v}")).unwrap_or_default(),
3200            )),
3201            "int" => match args.first() {
3202                Some(Value::Float(f)) => Ok(Value::Int(*f as i64)),
3203                Some(Value::String(s)) => s
3204                    .parse::<i64>()
3205                    .map(Value::Int)
3206                    .map_err(|_| runtime_err(format!("Cannot convert '{s}' to int"))),
3207                Some(Value::Int(n)) => Ok(Value::Int(*n)),
3208                Some(Value::Bool(b)) => Ok(Value::Int(if *b { 1 } else { 0 })),
3209                _ => Err(runtime_err(
3210                    "int() expects a number, string, or bool".to_string(),
3211                )),
3212            },
3213            "float" => match args.first() {
3214                Some(Value::Int(n)) => Ok(Value::Float(*n as f64)),
3215                Some(Value::String(s)) => s
3216                    .parse::<f64>()
3217                    .map(Value::Float)
3218                    .map_err(|_| runtime_err(format!("Cannot convert '{s}' to float"))),
3219                Some(Value::Float(n)) => Ok(Value::Float(*n)),
3220                Some(Value::Bool(b)) => Ok(Value::Float(if *b { 1.0 } else { 0.0 })),
3221                _ => Err(runtime_err(
3222                    "float() expects a number, string, or bool".to_string(),
3223                )),
3224            },
3225            "abs" => match args.first() {
3226                Some(Value::Int(n)) => Ok(Value::Int(n.abs())),
3227                Some(Value::Float(n)) => Ok(Value::Float(n.abs())),
3228                _ => Err(runtime_err("abs() expects a number".to_string())),
3229            },
3230            "min" => {
3231                if args.len() == 2 {
3232                    match (&args[0], &args[1]) {
3233                        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(*a.min(b))),
3234                        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a.min(*b))),
3235                        _ => Err(runtime_err("min() expects two numbers".to_string())),
3236                    }
3237                } else {
3238                    Err(runtime_err("min() expects 2 arguments".to_string()))
3239                }
3240            }
3241            "max" => {
3242                if args.len() == 2 {
3243                    match (&args[0], &args[1]) {
3244                        (Value::Int(a), Value::Int(b)) => Ok(Value::Int(*a.max(b))),
3245                        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a.max(*b))),
3246                        _ => Err(runtime_err("max() expects two numbers".to_string())),
3247                    }
3248                } else {
3249                    Err(runtime_err("max() expects 2 arguments".to_string()))
3250                }
3251            }
3252            "range" => {
3253                if args.len() == 1 {
3254                    if let Value::Int(n) = &args[0] {
3255                        if *n > 10_000_000 {
3256                            return Err(runtime_err(
3257                                "range() size too large (max 10,000,000)".to_string(),
3258                            ));
3259                        }
3260                        if *n < 0 {
3261                            return Ok(Value::List(vec![]));
3262                        }
3263                        Ok(Value::List((0..*n).map(Value::Int).collect()))
3264                    } else {
3265                        Err(runtime_err("range() expects an integer".to_string()))
3266                    }
3267                } else if args.len() == 2 {
3268                    if let (Value::Int(start), Value::Int(end)) = (&args[0], &args[1]) {
3269                        let size = (*end - *start).max(0);
3270                        if size > 10_000_000 {
3271                            return Err(runtime_err(
3272                                "range() size too large (max 10,000,000)".to_string(),
3273                            ));
3274                        }
3275                        Ok(Value::List((*start..*end).map(Value::Int).collect()))
3276                    } else {
3277                        Err(runtime_err("range() expects integers".to_string()))
3278                    }
3279                } else if args.len() == 3 {
3280                    if let (Value::Int(start), Value::Int(end), Value::Int(step)) =
3281                        (&args[0], &args[1], &args[2])
3282                    {
3283                        if *step == 0 {
3284                            return Err(runtime_err("range() step cannot be zero".to_string()));
3285                        }
3286                        let mut result = Vec::new();
3287                        let mut i = *start;
3288                        if *step > 0 {
3289                            while i < *end {
3290                                result.push(Value::Int(i));
3291                                i += step;
3292                            }
3293                        } else {
3294                            while i > *end {
3295                                result.push(Value::Int(i));
3296                                i += step;
3297                            }
3298                        }
3299                        Ok(Value::List(result))
3300                    } else {
3301                        Err(runtime_err("range() expects integers".to_string()))
3302                    }
3303                } else {
3304                    Err(runtime_err(
3305                        "range() expects 1, 2, or 3 arguments".to_string(),
3306                    ))
3307                }
3308            }
3309            "push" => {
3310                if args.len() == 2 {
3311                    if let Value::List(mut items) = args[0].clone() {
3312                        items.push(args[1].clone());
3313                        Ok(Value::List(items))
3314                    } else {
3315                        Err(runtime_err("push() first arg must be a list".to_string()))
3316                    }
3317                } else {
3318                    Err(runtime_err("push() expects 2 arguments".to_string()))
3319                }
3320            }
3321            "type_of" => Ok(Value::String(
3322                args.first()
3323                    .map(|v| v.type_name().to_string())
3324                    .unwrap_or_else(|| "none".to_string()),
3325            )),
3326            "map" => {
3327                if args.len() != 2 {
3328                    return Err(runtime_err(
3329                        "map() expects 2 arguments (list, fn)".to_string(),
3330                    ));
3331                }
3332                let items = match &args[0] {
3333                    Value::List(items) => items.clone(),
3334                    _ => return Err(runtime_err("map() first arg must be a list".to_string())),
3335                };
3336                let func = args[1].clone();
3337                let mut result = Vec::new();
3338                for item in items {
3339                    result.push(self.call_function(&func, &[item])?);
3340                }
3341                Ok(Value::List(result))
3342            }
3343            "filter" => {
3344                if args.len() != 2 {
3345                    return Err(runtime_err(
3346                        "filter() expects 2 arguments (list, fn)".to_string(),
3347                    ));
3348                }
3349                let items = match &args[0] {
3350                    Value::List(items) => items.clone(),
3351                    _ => return Err(runtime_err("filter() first arg must be a list".to_string())),
3352                };
3353                let func = args[1].clone();
3354                let mut result = Vec::new();
3355                for item in items {
3356                    let val = self.call_function(&func, std::slice::from_ref(&item))?;
3357                    if val.is_truthy() {
3358                        result.push(item);
3359                    }
3360                }
3361                Ok(Value::List(result))
3362            }
3363            "reduce" => {
3364                if args.len() != 3 {
3365                    return Err(runtime_err(
3366                        "reduce() expects 3 arguments (list, init, fn)".to_string(),
3367                    ));
3368                }
3369                let items = match &args[0] {
3370                    Value::List(items) => items.clone(),
3371                    _ => return Err(runtime_err("reduce() first arg must be a list".to_string())),
3372                };
3373                let mut acc = args[1].clone();
3374                let func = args[2].clone();
3375                for item in items {
3376                    acc = self.call_function(&func, &[acc, item])?;
3377                }
3378                Ok(acc)
3379            }
3380            "sum" => {
3381                if args.len() != 1 {
3382                    return Err(runtime_err("sum() expects 1 argument (list)".to_string()));
3383                }
3384                let items = match &args[0] {
3385                    Value::List(items) => items.clone(),
3386                    _ => return Err(runtime_err("sum() expects a list".to_string())),
3387                };
3388                let mut total: i64 = 0;
3389                let mut is_float = false;
3390                let mut total_f: f64 = 0.0;
3391                for item in &items {
3392                    match item {
3393                        Value::Int(n) => {
3394                            if is_float {
3395                                total_f += *n as f64;
3396                            } else {
3397                                total += n;
3398                            }
3399                        }
3400                        Value::Float(n) => {
3401                            if !is_float {
3402                                total_f = total as f64;
3403                                is_float = true;
3404                            }
3405                            total_f += n;
3406                        }
3407                        _ => {
3408                            return Err(runtime_err("sum() list must contain numbers".to_string()));
3409                        }
3410                    }
3411                }
3412                if is_float {
3413                    Ok(Value::Float(total_f))
3414                } else {
3415                    Ok(Value::Int(total))
3416                }
3417            }
3418            "any" => {
3419                if args.len() != 2 {
3420                    return Err(runtime_err(
3421                        "any() expects 2 arguments (list, fn)".to_string(),
3422                    ));
3423                }
3424                let items = match &args[0] {
3425                    Value::List(items) => items.clone(),
3426                    _ => return Err(runtime_err("any() first arg must be a list".to_string())),
3427                };
3428                let func = args[1].clone();
3429                for item in items {
3430                    let val = self.call_function(&func, &[item])?;
3431                    if val.is_truthy() {
3432                        return Ok(Value::Bool(true));
3433                    }
3434                }
3435                Ok(Value::Bool(false))
3436            }
3437            "all" => {
3438                if args.len() != 2 {
3439                    return Err(runtime_err(
3440                        "all() expects 2 arguments (list, fn)".to_string(),
3441                    ));
3442                }
3443                let items = match &args[0] {
3444                    Value::List(items) => items.clone(),
3445                    _ => return Err(runtime_err("all() first arg must be a list".to_string())),
3446                };
3447                let func = args[1].clone();
3448                for item in items {
3449                    let val = self.call_function(&func, &[item])?;
3450                    if !val.is_truthy() {
3451                        return Ok(Value::Bool(false));
3452                    }
3453                }
3454                Ok(Value::Bool(true))
3455            }
3456            // ── Data engine builtins ──
3457            "read_csv" => {
3458                if args.len() != 1 {
3459                    return Err(runtime_err("read_csv() expects 1 argument (path)".into()));
3460                }
3461                let path = match &args[0] {
3462                    Value::String(s) => s.clone(),
3463                    _ => return Err(runtime_err("read_csv() path must be a string".into())),
3464                };
3465                let df = self.engine().read_csv(&path).map_err(runtime_err)?;
3466                Ok(Value::Table(TlTable { df }))
3467            }
3468            "read_parquet" => {
3469                if args.len() != 1 {
3470                    return Err(runtime_err(
3471                        "read_parquet() expects 1 argument (path)".into(),
3472                    ));
3473                }
3474                let path = match &args[0] {
3475                    Value::String(s) => s.clone(),
3476                    _ => return Err(runtime_err("read_parquet() path must be a string".into())),
3477                };
3478                let df = self.engine().read_parquet(&path).map_err(runtime_err)?;
3479                Ok(Value::Table(TlTable { df }))
3480            }
3481            "write_csv" => {
3482                if args.len() != 2 {
3483                    return Err(runtime_err(
3484                        "write_csv() expects 2 arguments (table, path)".into(),
3485                    ));
3486                }
3487                let df = match &args[0] {
3488                    Value::Table(t) => t.df.clone(),
3489                    _ => return Err(runtime_err("write_csv() first arg must be a table".into())),
3490                };
3491                let path = match &args[1] {
3492                    Value::String(s) => s.clone(),
3493                    _ => return Err(runtime_err("write_csv() path must be a string".into())),
3494                };
3495                self.engine().write_csv(df, &path).map_err(runtime_err)?;
3496                Ok(Value::None)
3497            }
3498            "write_parquet" => {
3499                if args.len() != 2 {
3500                    return Err(runtime_err(
3501                        "write_parquet() expects 2 arguments (table, path)".into(),
3502                    ));
3503                }
3504                let df = match &args[0] {
3505                    Value::Table(t) => t.df.clone(),
3506                    _ => {
3507                        return Err(runtime_err(
3508                            "write_parquet() first arg must be a table".into(),
3509                        ));
3510                    }
3511                };
3512                let path = match &args[1] {
3513                    Value::String(s) => s.clone(),
3514                    _ => return Err(runtime_err("write_parquet() path must be a string".into())),
3515                };
3516                self.engine()
3517                    .write_parquet(df, &path)
3518                    .map_err(runtime_err)?;
3519                Ok(Value::None)
3520            }
3521            "collect" => {
3522                if args.len() != 1 {
3523                    return Err(runtime_err("collect() expects 1 argument (table)".into()));
3524                }
3525                let df = match &args[0] {
3526                    Value::Table(t) => t.df.clone(),
3527                    _ => return Err(runtime_err("collect() expects a table".into())),
3528                };
3529                let batches = self.engine().collect(df).map_err(runtime_err)?;
3530                let formatted = DataEngine::format_batches(&batches).map_err(runtime_err)?;
3531                Ok(Value::String(formatted))
3532            }
3533            "show" => {
3534                let df = match args.first() {
3535                    Some(Value::Table(t)) => t.df.clone(),
3536                    _ => return Err(runtime_err("show() expects a table".into())),
3537                };
3538                let limit = match args.get(1) {
3539                    Some(Value::Int(n)) => *n as usize,
3540                    None => 20,
3541                    _ => return Err(runtime_err("show() second arg must be an int".into())),
3542                };
3543                let limited = df
3544                    .limit(0, Some(limit))
3545                    .map_err(|e| runtime_err(format!("{e}")))?;
3546                let batches = self.engine().collect(limited).map_err(runtime_err)?;
3547                let formatted = DataEngine::format_batches(&batches).map_err(runtime_err)?;
3548                println!("{formatted}");
3549                self.output.push(formatted.clone());
3550                Ok(Value::None)
3551            }
3552            "describe" => {
3553                if args.len() != 1 {
3554                    return Err(runtime_err("describe() expects 1 argument (table)".into()));
3555                }
3556                let df = match &args[0] {
3557                    Value::Table(t) => t.df.clone(),
3558                    _ => return Err(runtime_err("describe() expects a table".into())),
3559                };
3560                let schema = df.schema();
3561                let mut lines = Vec::new();
3562                lines.push("Columns:".to_string());
3563                for (qualifier, field) in schema.iter() {
3564                    let prefix = match qualifier {
3565                        Some(q) => format!("{q}."),
3566                        None => String::new(),
3567                    };
3568                    lines.push(format!(
3569                        "  {}{}: {}",
3570                        prefix,
3571                        field.name(),
3572                        field.data_type()
3573                    ));
3574                }
3575                let output = lines.join("\n");
3576                println!("{output}");
3577                self.output.push(output.clone());
3578                Ok(Value::String(output))
3579            }
3580            "head" => {
3581                if args.is_empty() {
3582                    return Err(runtime_err(
3583                        "head() expects at least 1 argument (table)".into(),
3584                    ));
3585                }
3586                let df = match &args[0] {
3587                    Value::Table(t) => t.df.clone(),
3588                    _ => return Err(runtime_err("head() first arg must be a table".into())),
3589                };
3590                let n = match args.get(1) {
3591                    Some(Value::Int(n)) => *n as usize,
3592                    None => 10,
3593                    _ => return Err(runtime_err("head() second arg must be an int".into())),
3594                };
3595                let limited = df
3596                    .limit(0, Some(n))
3597                    .map_err(|e| runtime_err(format!("{e}")))?;
3598                Ok(Value::Table(TlTable { df: limited }))
3599            }
3600            "postgres" | "read_postgres" => {
3601                if args.len() != 2 {
3602                    return Err(runtime_err(
3603                        "postgres() expects 2 arguments (conn_str, table_name)".into(),
3604                    ));
3605                }
3606                let conn_str = match &args[0] {
3607                    Value::String(s) => s.clone(),
3608                    _ => return Err(runtime_err("postgres() conn_str must be a string".into())),
3609                };
3610                let table_name = match &args[1] {
3611                    Value::String(s) => s.clone(),
3612                    _ => return Err(runtime_err("postgres() table_name must be a string".into())),
3613                };
3614                let conn_str = resolve_tl_config_connection_interp(&conn_str);
3615                let df = self
3616                    .engine()
3617                    .read_postgres(&conn_str, &table_name)
3618                    .map_err(runtime_err)?;
3619                Ok(Value::Table(TlTable { df }))
3620            }
3621            "postgres_query" => {
3622                if args.len() != 2 {
3623                    return Err(runtime_err(
3624                        "postgres_query() expects 2 arguments (conn_str, query)".into(),
3625                    ));
3626                }
3627                let conn_str = match &args[0] {
3628                    Value::String(s) => s.clone(),
3629                    _ => {
3630                        return Err(runtime_err(
3631                            "postgres_query() conn_str must be a string".into(),
3632                        ));
3633                    }
3634                };
3635                let query = match &args[1] {
3636                    Value::String(s) => s.clone(),
3637                    _ => {
3638                        return Err(runtime_err(
3639                            "postgres_query() query must be a string".into(),
3640                        ));
3641                    }
3642                };
3643                let conn_str = resolve_tl_config_connection_interp(&conn_str);
3644                let df = self
3645                    .engine()
3646                    .query_postgres(&conn_str, &query, "__pg_query_result")
3647                    .map_err(runtime_err)?;
3648                Ok(Value::Table(TlTable { df }))
3649            }
3650            "fold" => {
3651                // fold is an alias for reduce
3652                self.call_builtin("reduce", args)
3653            }
3654            "tl_config_resolve" => {
3655                if args.len() != 1 {
3656                    return Err(runtime_err(
3657                        "tl_config_resolve() expects 1 argument (name)".into(),
3658                    ));
3659                }
3660                let name = match &args[0] {
3661                    Value::String(s) => s.clone(),
3662                    _ => {
3663                        return Err(runtime_err(
3664                            "tl_config_resolve() name must be a string".into(),
3665                        ));
3666                    }
3667                };
3668                let resolved = resolve_tl_config_connection_interp(&name);
3669                Ok(Value::String(resolved))
3670            }
3671            // ── AI builtins ──
3672            "tensor" => self.builtin_tensor(args),
3673            "tensor_zeros" => self.builtin_tensor_zeros(args),
3674            "tensor_ones" => self.builtin_tensor_ones(args),
3675            "tensor_shape" => self.builtin_tensor_shape(args),
3676            "tensor_reshape" => self.builtin_tensor_reshape(args),
3677            "tensor_transpose" => self.builtin_tensor_transpose(args),
3678            "tensor_sum" => self.builtin_tensor_sum(args),
3679            "tensor_mean" => self.builtin_tensor_mean(args),
3680            "tensor_dot" => self.builtin_tensor_dot(args),
3681            "predict" => self.builtin_predict(args),
3682            "similarity" => self.builtin_similarity(args),
3683            "ai_complete" => self.builtin_ai_complete(args),
3684            "ai_chat" => self.builtin_ai_chat(args),
3685            "model_save" => self.builtin_model_save(args),
3686            "model_load" => self.builtin_model_load(args),
3687            "model_register" => self.builtin_model_register(args),
3688            "model_list" => self.builtin_model_list(args),
3689            "model_get" => self.builtin_model_get(args),
3690            "embed" => {
3691                if args.is_empty() {
3692                    return Err(runtime_err("embed() requires a text argument".to_string()));
3693                }
3694                let text = match &args[0] {
3695                    Value::String(s) => s.clone(),
3696                    _ => return Err(runtime_err("embed() expects a string".to_string())),
3697                };
3698                let model = args
3699                    .get(1)
3700                    .and_then(|v| {
3701                        if let Value::String(s) = v {
3702                            Some(s.clone())
3703                        } else {
3704                            None
3705                        }
3706                    })
3707                    .unwrap_or_else(|| "text-embedding-3-small".to_string());
3708                let api_key = args
3709                    .get(2)
3710                    .and_then(|v| {
3711                        if let Value::String(s) = v {
3712                            Some(s.clone())
3713                        } else {
3714                            None
3715                        }
3716                    })
3717                    .or_else(|| std::env::var("TL_OPENAI_KEY").ok())
3718                    .ok_or_else(|| {
3719                        runtime_err(
3720                            "embed() requires an API key. Set TL_OPENAI_KEY or pass as 3rd arg"
3721                                .to_string(),
3722                        )
3723                    })?;
3724                let tensor = tl_ai::embed::embed_api(&text, "openai", &model, &api_key)
3725                    .map_err(|e| runtime_err(format!("embed error: {e}")))?;
3726                Ok(Value::Tensor(tensor))
3727            }
3728            // Streaming builtins
3729            "alert_slack" => {
3730                if args.len() != 2 {
3731                    return Err(runtime_err(
3732                        "alert_slack(url, message) requires 2 args".to_string(),
3733                    ));
3734                }
3735                let url = match &args[0] {
3736                    Value::String(s) => s.clone(),
3737                    _ => return Err(runtime_err("alert_slack: url must be a string".to_string())),
3738                };
3739                let msg = match &args[1] {
3740                    Value::String(s) => s.clone(),
3741                    _ => format!("{}", args[1]),
3742                };
3743                tl_stream::send_alert(&tl_stream::AlertTarget::Slack(url), &msg)
3744                    .map_err(runtime_err)?;
3745                Ok(Value::None)
3746            }
3747            "alert_webhook" => {
3748                if args.len() != 2 {
3749                    return Err(runtime_err(
3750                        "alert_webhook(url, message) requires 2 args".to_string(),
3751                    ));
3752                }
3753                let url = match &args[0] {
3754                    Value::String(s) => s.clone(),
3755                    _ => {
3756                        return Err(runtime_err(
3757                            "alert_webhook: url must be a string".to_string(),
3758                        ));
3759                    }
3760                };
3761                let msg = match &args[1] {
3762                    Value::String(s) => s.clone(),
3763                    _ => format!("{}", args[1]),
3764                };
3765                tl_stream::send_alert(&tl_stream::AlertTarget::Webhook(url), &msg)
3766                    .map_err(runtime_err)?;
3767                Ok(Value::None)
3768            }
3769            "emit" => {
3770                // emit(value) — output a value in a stream context
3771                if args.is_empty() {
3772                    return Err(runtime_err(
3773                        "emit() requires at least 1 argument".to_string(),
3774                    ));
3775                }
3776                let val = &args[0];
3777                self.output.push(format!("emit: {val}"));
3778                Ok(val.clone())
3779            }
3780            "lineage" => {
3781                // lineage() — create a new lineage tracker
3782                // For now, return a string representation
3783                Ok(Value::String("lineage_tracker".to_string()))
3784            }
3785            "run_pipeline" => {
3786                if args.is_empty() {
3787                    return Err(runtime_err(
3788                        "run_pipeline() requires a pipeline name".to_string(),
3789                    ));
3790                }
3791                if let Value::Pipeline(ref def) = args[0] {
3792                    Ok(Value::String(format!("Pipeline '{}' triggered", def.name)))
3793                } else {
3794                    Err(runtime_err(
3795                        "run_pipeline: argument must be a pipeline".to_string(),
3796                    ))
3797                }
3798            }
3799            // Math builtins
3800            "sqrt" => match args.first() {
3801                Some(Value::Float(n)) => Ok(Value::Float(n.sqrt())),
3802                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).sqrt())),
3803                _ => Err(runtime_err_s("sqrt() expects a number")),
3804            },
3805            "pow" => {
3806                if args.len() == 2 {
3807                    match (&args[0], &args[1]) {
3808                        (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a.powf(*b))),
3809                        (Value::Int(a), Value::Int(b)) => {
3810                            Ok(Value::Float((*a as f64).powf(*b as f64)))
3811                        }
3812                        (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a.powf(*b as f64))),
3813                        (Value::Int(a), Value::Float(b)) => Ok(Value::Float((*a as f64).powf(*b))),
3814                        _ => Err(runtime_err_s("pow() expects two numbers")),
3815                    }
3816                } else {
3817                    Err(runtime_err_s("pow() expects 2 arguments"))
3818                }
3819            }
3820            "floor" => match args.first() {
3821                Some(Value::Float(n)) => Ok(Value::Float(n.floor())),
3822                Some(Value::Int(n)) => Ok(Value::Int(*n)),
3823                _ => Err(runtime_err_s("floor() expects a number")),
3824            },
3825            "ceil" => match args.first() {
3826                Some(Value::Float(n)) => Ok(Value::Float(n.ceil())),
3827                Some(Value::Int(n)) => Ok(Value::Int(*n)),
3828                _ => Err(runtime_err_s("ceil() expects a number")),
3829            },
3830            "round" => match args.first() {
3831                Some(Value::Float(n)) => Ok(Value::Float(n.round())),
3832                Some(Value::Int(n)) => Ok(Value::Int(*n)),
3833                _ => Err(runtime_err_s("round() expects a number")),
3834            },
3835            "sin" => match args.first() {
3836                Some(Value::Float(n)) => Ok(Value::Float(n.sin())),
3837                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).sin())),
3838                _ => Err(runtime_err_s("sin() expects a number")),
3839            },
3840            "cos" => match args.first() {
3841                Some(Value::Float(n)) => Ok(Value::Float(n.cos())),
3842                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).cos())),
3843                _ => Err(runtime_err_s("cos() expects a number")),
3844            },
3845            "tan" => match args.first() {
3846                Some(Value::Float(n)) => Ok(Value::Float(n.tan())),
3847                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).tan())),
3848                _ => Err(runtime_err_s("tan() expects a number")),
3849            },
3850            "log" => match args.first() {
3851                Some(Value::Float(n)) => Ok(Value::Float(n.ln())),
3852                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).ln())),
3853                _ => Err(runtime_err_s("log() expects a number")),
3854            },
3855            "log2" => match args.first() {
3856                Some(Value::Float(n)) => Ok(Value::Float(n.log2())),
3857                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).log2())),
3858                _ => Err(runtime_err_s("log2() expects a number")),
3859            },
3860            "log10" => match args.first() {
3861                Some(Value::Float(n)) => Ok(Value::Float(n.log10())),
3862                Some(Value::Int(n)) => Ok(Value::Float((*n as f64).log10())),
3863                _ => Err(runtime_err_s("log10() expects a number")),
3864            },
3865            "join" => {
3866                if args.len() == 2 {
3867                    if let (Value::String(sep), Value::List(items)) = (&args[0], &args[1]) {
3868                        let parts: Vec<String> = items.iter().map(|v| format!("{v}")).collect();
3869                        Ok(Value::String(parts.join(sep.as_str())))
3870                    } else {
3871                        Err(runtime_err_s(
3872                            "join() expects a separator string and a list",
3873                        ))
3874                    }
3875                } else {
3876                    Err(runtime_err_s("join() expects 2 arguments"))
3877                }
3878            }
3879            // Assert builtins
3880            "assert" => {
3881                if args.is_empty() {
3882                    return Err(runtime_err_s("assert() expects at least 1 argument"));
3883                }
3884                if !args[0].is_truthy() {
3885                    let msg = if args.len() > 1 {
3886                        format!("{}", args[1])
3887                    } else {
3888                        "Assertion failed".to_string()
3889                    };
3890                    Err(runtime_err(msg))
3891                } else {
3892                    Ok(Value::None)
3893                }
3894            }
3895            "assert_eq" => {
3896                if args.len() < 2 {
3897                    return Err(runtime_err_s("assert_eq() expects 2 arguments"));
3898                }
3899                let eq = match (&args[0], &args[1]) {
3900                    (Value::Int(a), Value::Int(b)) => a == b,
3901                    (Value::Float(a), Value::Float(b)) => a == b,
3902                    (Value::String(a), Value::String(b)) => a == b,
3903                    (Value::Bool(a), Value::Bool(b)) => a == b,
3904                    (Value::None, Value::None) => true,
3905                    _ => false,
3906                };
3907                if !eq {
3908                    Err(runtime_err(format!(
3909                        "Assertion failed: {} != {}",
3910                        args[0], args[1]
3911                    )))
3912                } else {
3913                    Ok(Value::None)
3914                }
3915            }
3916            "assert_table_eq" => {
3917                // Table comparison not available in interpreter (DataFusion is VM-only)
3918                Err(runtime_err(
3919                    "assert_table_eq() is only available in the VM backend".to_string(),
3920                ))
3921            }
3922            // HTTP builtins
3923            "http_get" => {
3924                if args.is_empty() {
3925                    return Err(runtime_err_s("http_get() expects a URL string"));
3926                }
3927                if let Value::String(url) = &args[0] {
3928                    let body = reqwest::blocking::get(url.as_str())
3929                        .map_err(|e| runtime_err(format!("HTTP GET error: {e}")))?
3930                        .text()
3931                        .map_err(|e| runtime_err(format!("HTTP response error: {e}")))?;
3932                    Ok(Value::String(body))
3933                } else {
3934                    Err(runtime_err_s("http_get() expects a string URL"))
3935                }
3936            }
3937            "http_post" => {
3938                if args.len() < 2 {
3939                    return Err(runtime_err_s("http_post() expects a URL and body string"));
3940                }
3941                if let (Value::String(url), Value::String(body_str)) = (&args[0], &args[1]) {
3942                    let client = reqwest::blocking::Client::new();
3943                    let resp = client
3944                        .post(url.as_str())
3945                        .header("Content-Type", "application/json")
3946                        .body(body_str.clone())
3947                        .send()
3948                        .map_err(|e| runtime_err(format!("HTTP POST error: {e}")))?
3949                        .text()
3950                        .map_err(|e| runtime_err(format!("HTTP response error: {e}")))?;
3951                    Ok(Value::String(resp))
3952                } else {
3953                    Err(runtime_err_s("http_post() expects string URL and body"))
3954                }
3955            }
3956            "http_request" => {
3957                if args.len() < 2 {
3958                    return Err(runtime_err_s(
3959                        "http_request(method, url, headers?, body?) expects at least 2 args",
3960                    ));
3961                }
3962                let method = match &args[0] {
3963                    Value::String(s) => s.clone(),
3964                    _ => return Err(runtime_err_s("http_request() method must be a string")),
3965                };
3966                let url = match &args[1] {
3967                    Value::String(s) => s.clone(),
3968                    _ => return Err(runtime_err_s("http_request() url must be a string")),
3969                };
3970                let client = reqwest::blocking::Client::new();
3971                let mut builder = match method.to_uppercase().as_str() {
3972                    "GET" => client.get(&url),
3973                    "POST" => client.post(&url),
3974                    "PUT" => client.put(&url),
3975                    "DELETE" => client.delete(&url),
3976                    "PATCH" => client.patch(&url),
3977                    "HEAD" => client.head(&url),
3978                    _ => return Err(runtime_err(format!("Unsupported HTTP method: {method}"))),
3979                };
3980                if let Some(Value::Map(headers)) = args.get(2) {
3981                    for (key, val) in headers {
3982                        if let Value::String(v) = val {
3983                            builder = builder.header(key.as_str(), v.as_str());
3984                        }
3985                    }
3986                }
3987                if let Some(Value::String(body)) = args.get(3) {
3988                    builder = builder.body(body.clone());
3989                }
3990                let resp = builder
3991                    .send()
3992                    .map_err(|e| runtime_err(format!("HTTP error: {e}")))?;
3993                let status = resp.status().as_u16() as i64;
3994                let body = resp
3995                    .text()
3996                    .map_err(|e| runtime_err(format!("HTTP response error: {e}")))?;
3997                Ok(Value::Map(vec![
3998                    ("status".to_string(), Value::Int(status)),
3999                    ("body".to_string(), Value::String(body)),
4000                ]))
4001            }
4002            "run_agent" => {
4003                if args.len() < 2 {
4004                    return Err(runtime_err_s(
4005                        "run_agent(agent, message, [history]) expects at least 2 arguments",
4006                    ));
4007                }
4008                let agent_def = match &args[0] {
4009                    Value::Agent(def) => def.clone(),
4010                    _ => return Err(runtime_err_s("run_agent() first arg must be an agent")),
4011                };
4012                let message = match &args[1] {
4013                    Value::String(s) => s.clone(),
4014                    _ => return Err(runtime_err_s("run_agent() second arg must be a string")),
4015                };
4016                // Optional 3rd arg: conversation history as list of [role, content] pairs
4017                let history = if args.len() >= 3 {
4018                    match &args[2] {
4019                        Value::List(items) => {
4020                            let mut hist = Vec::new();
4021                            for item in items {
4022                                if let Value::List(pair) = item
4023                                    && pair.len() >= 2
4024                                {
4025                                    let role = match &pair[0] {
4026                                        Value::String(s) => s.clone(),
4027                                        _ => continue,
4028                                    };
4029                                    let content = match &pair[1] {
4030                                        Value::String(s) => s.clone(),
4031                                        _ => continue,
4032                                    };
4033                                    hist.push((role, content));
4034                                }
4035                            }
4036                            Some(hist)
4037                        }
4038                        _ => None,
4039                    }
4040                } else {
4041                    None
4042                };
4043                self.exec_agent_loop(&agent_def, &message, history.as_deref())
4044            }
4045            // Phase G4: Streaming agent responses
4046            "stream_agent" => {
4047                if args.len() < 3 {
4048                    return Err(runtime_err_s(
4049                        "stream_agent(agent, message, callback) expects 3 arguments",
4050                    ));
4051                }
4052                let agent_def = match &args[0] {
4053                    Value::Agent(def) => def.clone(),
4054                    _ => return Err(runtime_err_s("stream_agent() first arg must be an agent")),
4055                };
4056                let message = match &args[1] {
4057                    Value::String(s) => s.clone(),
4058                    _ => return Err(runtime_err_s("stream_agent() second arg must be a string")),
4059                };
4060                let callback = args[2].clone();
4061
4062                let model = &agent_def.model;
4063                let system = agent_def.system_prompt.as_deref();
4064                let base_url = agent_def.base_url.as_deref();
4065                let api_key = agent_def.api_key.as_deref();
4066
4067                let messages = vec![serde_json::json!({"role": "user", "content": &message})];
4068                let mut reader = tl_ai::stream_chat(model, system, &messages, base_url, api_key)
4069                    .map_err(|e| runtime_err(format!("Stream error: {e}")))?;
4070
4071                let mut full_text = String::new();
4072                loop {
4073                    match reader.next_chunk() {
4074                        Ok(Some(chunk)) => {
4075                            full_text.push_str(&chunk);
4076                            let chunk_val = Value::String(chunk);
4077                            let _ = self.call_function_value(&callback, &[chunk_val]);
4078                        }
4079                        Ok(None) => break,
4080                        Err(e) => return Err(runtime_err(format!("Stream error: {e}"))),
4081                    }
4082                }
4083
4084                Ok(Value::String(full_text))
4085            }
4086            // ── Phase 6: Stdlib & Ecosystem builtins ──
4087            "json_parse" => {
4088                if args.is_empty() {
4089                    return Err(runtime_err_s("json_parse() expects a string"));
4090                }
4091                if let Value::String(s) = &args[0] {
4092                    let json_val: serde_json::Value = serde_json::from_str(s)
4093                        .map_err(|e| runtime_err(format!("JSON parse error: {e}")))?;
4094                    Ok(json_to_value(&json_val))
4095                } else {
4096                    Err(runtime_err_s("json_parse() expects a string"))
4097                }
4098            }
4099            "json_stringify" => {
4100                if args.is_empty() {
4101                    return Err(runtime_err_s("json_stringify() expects a value"));
4102                }
4103                let json = value_to_json(&args[0]);
4104                Ok(Value::String(json.to_string()))
4105            }
4106            "map_from" => {
4107                if !args.len().is_multiple_of(2) {
4108                    return Err(runtime_err_s(
4109                        "map_from() expects even number of arguments (key, value pairs)",
4110                    ));
4111                }
4112                let mut pairs = Vec::new();
4113                for chunk in args.chunks(2) {
4114                    let key = match &chunk[0] {
4115                        Value::String(s) => s.clone(),
4116                        other => format!("{other}"),
4117                    };
4118                    pairs.push((key, chunk[1].clone()));
4119                }
4120                Ok(Value::Map(pairs))
4121            }
4122            "read_file" => {
4123                if args.is_empty() {
4124                    return Err(runtime_err_s("read_file() expects a path"));
4125                }
4126                if let Value::String(path) = &args[0] {
4127                    let content = std::fs::read_to_string(path)
4128                        .map_err(|e| runtime_err(format!("read_file error: {e}")))?;
4129                    Ok(Value::String(content))
4130                } else {
4131                    Err(runtime_err_s("read_file() expects a string path"))
4132                }
4133            }
4134            "write_file" => {
4135                if args.len() < 2 {
4136                    return Err(runtime_err_s("write_file() expects path and content"));
4137                }
4138                if let (Value::String(path), Value::String(content)) = (&args[0], &args[1]) {
4139                    std::fs::write(path, content)
4140                        .map_err(|e| runtime_err(format!("write_file error: {e}")))?;
4141                    Ok(Value::None)
4142                } else {
4143                    Err(runtime_err_s(
4144                        "write_file() expects string path and content",
4145                    ))
4146                }
4147            }
4148            "append_file" => {
4149                if args.len() < 2 {
4150                    return Err(runtime_err_s("append_file() expects path and content"));
4151                }
4152                if let (Value::String(path), Value::String(content)) = (&args[0], &args[1]) {
4153                    use std::io::Write;
4154                    let mut file = std::fs::OpenOptions::new()
4155                        .create(true)
4156                        .append(true)
4157                        .open(path)
4158                        .map_err(|e| runtime_err(format!("append_file error: {e}")))?;
4159                    file.write_all(content.as_bytes())
4160                        .map_err(|e| runtime_err(format!("append_file error: {e}")))?;
4161                    Ok(Value::None)
4162                } else {
4163                    Err(runtime_err_s(
4164                        "append_file() expects string path and content",
4165                    ))
4166                }
4167            }
4168            "file_exists" => {
4169                if args.is_empty() {
4170                    return Err(runtime_err_s("file_exists() expects a path"));
4171                }
4172                if let Value::String(path) = &args[0] {
4173                    Ok(Value::Bool(std::path::Path::new(path).exists()))
4174                } else {
4175                    Err(runtime_err_s("file_exists() expects a string path"))
4176                }
4177            }
4178            "list_dir" => {
4179                if args.is_empty() {
4180                    return Err(runtime_err_s("list_dir() expects a path"));
4181                }
4182                if let Value::String(path) = &args[0] {
4183                    let entries: Vec<Value> = std::fs::read_dir(path)
4184                        .map_err(|e| runtime_err(format!("list_dir error: {e}")))?
4185                        .filter_map(|e| e.ok())
4186                        .map(|e| Value::String(e.file_name().to_string_lossy().to_string()))
4187                        .collect();
4188                    Ok(Value::List(entries))
4189                } else {
4190                    Err(runtime_err_s("list_dir() expects a string path"))
4191                }
4192            }
4193            "env_get" => {
4194                if args.is_empty() {
4195                    return Err(runtime_err_s("env_get() expects a name"));
4196                }
4197                if let Value::String(name) = &args[0] {
4198                    match std::env::var(name) {
4199                        Ok(val) => Ok(Value::String(val)),
4200                        Err(_) => Ok(Value::None),
4201                    }
4202                } else {
4203                    Err(runtime_err_s("env_get() expects a string"))
4204                }
4205            }
4206            "env_set" => {
4207                if args.len() < 2 {
4208                    return Err(runtime_err_s("env_set() expects name and value"));
4209                }
4210                if let (Value::String(name), Value::String(val)) = (&args[0], &args[1]) {
4211                    unsafe {
4212                        std::env::set_var(name, val);
4213                    }
4214                    Ok(Value::None)
4215                } else {
4216                    Err(runtime_err_s("env_set() expects two strings"))
4217                }
4218            }
4219            "regex_match" => {
4220                if args.len() < 2 {
4221                    return Err(runtime_err_s("regex_match() expects pattern and string"));
4222                }
4223                if let (Value::String(pattern), Value::String(text)) = (&args[0], &args[1]) {
4224                    let re = regex::Regex::new(pattern)
4225                        .map_err(|e| runtime_err(format!("Invalid regex: {e}")))?;
4226                    Ok(Value::Bool(re.is_match(text)))
4227                } else {
4228                    Err(runtime_err_s(
4229                        "regex_match() expects string pattern and string",
4230                    ))
4231                }
4232            }
4233            "regex_find" => {
4234                if args.len() < 2 {
4235                    return Err(runtime_err_s("regex_find() expects pattern and string"));
4236                }
4237                if let (Value::String(pattern), Value::String(text)) = (&args[0], &args[1]) {
4238                    let re = regex::Regex::new(pattern)
4239                        .map_err(|e| runtime_err(format!("Invalid regex: {e}")))?;
4240                    let matches: Vec<Value> = re
4241                        .find_iter(text)
4242                        .map(|m| Value::String(m.as_str().to_string()))
4243                        .collect();
4244                    Ok(Value::List(matches))
4245                } else {
4246                    Err(runtime_err_s(
4247                        "regex_find() expects string pattern and string",
4248                    ))
4249                }
4250            }
4251            "regex_replace" => {
4252                if args.len() < 3 {
4253                    return Err(runtime_err_s(
4254                        "regex_replace() expects pattern, string, replacement",
4255                    ));
4256                }
4257                if let (Value::String(pattern), Value::String(text), Value::String(replacement)) =
4258                    (&args[0], &args[1], &args[2])
4259                {
4260                    let re = regex::Regex::new(pattern)
4261                        .map_err(|e| runtime_err(format!("Invalid regex: {e}")))?;
4262                    Ok(Value::String(
4263                        re.replace_all(text, replacement.as_str()).to_string(),
4264                    ))
4265                } else {
4266                    Err(runtime_err_s("regex_replace() expects three strings"))
4267                }
4268            }
4269            "now" => {
4270                let ts = chrono::Utc::now().timestamp_millis();
4271                Ok(Value::Int(ts))
4272            }
4273            "date_format" => {
4274                if args.len() < 2 {
4275                    return Err(runtime_err_s(
4276                        "date_format() expects timestamp_ms and format",
4277                    ));
4278                }
4279                if let (Value::Int(ts), Value::String(fmt)) = (&args[0], &args[1]) {
4280                    use chrono::TimeZone;
4281                    let secs = *ts / 1000;
4282                    let nsecs = ((*ts % 1000) * 1_000_000) as u32;
4283                    let dt = chrono::Utc
4284                        .timestamp_opt(secs, nsecs)
4285                        .single()
4286                        .ok_or_else(|| runtime_err_s("Invalid timestamp"))?;
4287                    Ok(Value::String(dt.format(fmt).to_string()))
4288                } else {
4289                    Err(runtime_err_s(
4290                        "date_format() expects int timestamp and string format",
4291                    ))
4292                }
4293            }
4294            "date_parse" => {
4295                if args.len() < 2 {
4296                    return Err(runtime_err_s("date_parse() expects string and format"));
4297                }
4298                if let (Value::String(s), Value::String(fmt)) = (&args[0], &args[1]) {
4299                    let dt = chrono::NaiveDateTime::parse_from_str(s, fmt)
4300                        .map_err(|e| runtime_err(format!("date_parse error: {e}")))?;
4301                    let ts = dt.and_utc().timestamp_millis();
4302                    Ok(Value::Int(ts))
4303                } else {
4304                    Err(runtime_err_s("date_parse() expects two strings"))
4305                }
4306            }
4307            "zip" => {
4308                if args.len() < 2 {
4309                    return Err(runtime_err_s("zip() expects two lists"));
4310                }
4311                if let (Value::List(a), Value::List(b)) = (&args[0], &args[1]) {
4312                    let pairs: Vec<Value> = a
4313                        .iter()
4314                        .zip(b.iter())
4315                        .map(|(x, y)| Value::List(vec![x.clone(), y.clone()]))
4316                        .collect();
4317                    Ok(Value::List(pairs))
4318                } else {
4319                    Err(runtime_err_s("zip() expects two lists"))
4320                }
4321            }
4322            "enumerate" => {
4323                if args.is_empty() {
4324                    return Err(runtime_err_s("enumerate() expects a list"));
4325                }
4326                if let Value::List(items) = &args[0] {
4327                    let pairs: Vec<Value> = items
4328                        .iter()
4329                        .enumerate()
4330                        .map(|(i, v)| Value::List(vec![Value::Int(i as i64), v.clone()]))
4331                        .collect();
4332                    Ok(Value::List(pairs))
4333                } else {
4334                    Err(runtime_err_s("enumerate() expects a list"))
4335                }
4336            }
4337            "bool" => {
4338                if args.is_empty() {
4339                    return Err(runtime_err_s("bool() expects a value"));
4340                }
4341                Ok(Value::Bool(args[0].is_truthy()))
4342            }
4343
4344            // Phase 7: Concurrency builtins
4345            "spawn" => {
4346                if args.is_empty() {
4347                    return Err(runtime_err_s("spawn() expects a function argument"));
4348                }
4349                match &args[0] {
4350                    Value::Function {
4351                        params, body, name, ..
4352                    } => {
4353                        let params = params.clone();
4354                        let body = body.clone();
4355                        let _name = name.clone();
4356                        let (tx, rx) = mpsc::channel::<Result<Value, String>>();
4357                        // Capture the global scope for the spawned thread
4358                        let env_scopes = self.env.scopes.clone();
4359                        let method_table = self.method_table.clone();
4360                        std::thread::spawn(move || {
4361                            let mut interp = Interpreter::new();
4362                            interp.env.scopes = env_scopes;
4363                            interp.method_table = method_table;
4364                            // Execute function body
4365                            interp.env.push_scope();
4366                            for param in &params {
4367                                interp.env.set(param.name.clone(), Value::None);
4368                            }
4369                            let mut result = Value::None;
4370                            let mut err = None;
4371                            for stmt in &body {
4372                                match interp.exec_stmt(stmt) {
4373                                    Ok(Signal::Return(val)) => {
4374                                        result = val;
4375                                        break;
4376                                    }
4377                                    Ok(Signal::None) => {
4378                                        if let Some(val) = &interp.last_expr_value {
4379                                            result = val.clone();
4380                                        }
4381                                    }
4382                                    Ok(Signal::Throw(val)) => {
4383                                        err = Some(format!("{val}"));
4384                                        break;
4385                                    }
4386                                    Err(e) => {
4387                                        err = Some(format!("{e}"));
4388                                        break;
4389                                    }
4390                                    _ => {}
4391                                }
4392                            }
4393                            interp.env.pop_scope();
4394                            let _ = tx.send(match err {
4395                                Some(e) => Err(e),
4396                                None => Ok(result),
4397                            });
4398                        });
4399                        Ok(Value::Task(Arc::new(TlTask::new(rx))))
4400                    }
4401                    Value::Closure {
4402                        params,
4403                        body,
4404                        captured_env,
4405                    } => {
4406                        let params = params.clone();
4407                        let body = body.clone();
4408                        let captured_env = captured_env.clone();
4409                        let (tx, rx) = mpsc::channel::<Result<Value, String>>();
4410                        let method_table = self.method_table.clone();
4411                        std::thread::spawn(move || {
4412                            let mut interp = Interpreter::new();
4413                            interp.env.scopes = captured_env;
4414                            interp.method_table = method_table;
4415                            interp.env.push_scope();
4416                            for param in &params {
4417                                interp.env.set(param.name.clone(), Value::None);
4418                            }
4419                            let result = interp.eval_closure_body(&body);
4420                            interp.env.pop_scope();
4421                            let _ = tx.send(result.map_err(|e| format!("{e}")));
4422                        });
4423                        Ok(Value::Task(Arc::new(TlTask::new(rx))))
4424                    }
4425                    _ => Err(runtime_err_s("spawn() expects a function")),
4426                }
4427            }
4428            "sleep" => {
4429                if args.is_empty() {
4430                    return Err(runtime_err_s("sleep() expects a duration in milliseconds"));
4431                }
4432                match &args[0] {
4433                    Value::Int(ms) => {
4434                        std::thread::sleep(Duration::from_millis(*ms as u64));
4435                        Ok(Value::None)
4436                    }
4437                    _ => Err(runtime_err_s("sleep() expects an integer (milliseconds)")),
4438                }
4439            }
4440            "channel" => {
4441                let capacity = match args.first() {
4442                    Some(Value::Int(n)) => *n as usize,
4443                    None => 64,
4444                    _ => {
4445                        return Err(runtime_err_s(
4446                            "channel() expects an optional integer capacity",
4447                        ));
4448                    }
4449                };
4450                Ok(Value::Channel(Arc::new(TlChannel::new(capacity))))
4451            }
4452            "send" => {
4453                if args.len() < 2 {
4454                    return Err(runtime_err_s("send() expects a channel and a value"));
4455                }
4456                match &args[0] {
4457                    Value::Channel(ch) => {
4458                        ch.sender
4459                            .send(args[1].clone())
4460                            .map_err(|_| runtime_err_s("Channel disconnected"))?;
4461                        Ok(Value::None)
4462                    }
4463                    _ => Err(runtime_err_s("send() expects a channel as first argument")),
4464                }
4465            }
4466            "recv" => {
4467                if args.is_empty() {
4468                    return Err(runtime_err_s("recv() expects a channel"));
4469                }
4470                match &args[0] {
4471                    Value::Channel(ch) => {
4472                        let guard = ch.receiver.lock().unwrap_or_else(|e| e.into_inner());
4473                        match guard.recv() {
4474                            Ok(val) => Ok(val),
4475                            Err(_) => Ok(Value::None),
4476                        }
4477                    }
4478                    _ => Err(runtime_err_s("recv() expects a channel")),
4479                }
4480            }
4481            "try_recv" => {
4482                if args.is_empty() {
4483                    return Err(runtime_err_s("try_recv() expects a channel"));
4484                }
4485                match &args[0] {
4486                    Value::Channel(ch) => {
4487                        let guard = ch.receiver.lock().unwrap_or_else(|e| e.into_inner());
4488                        match guard.try_recv() {
4489                            Ok(val) => Ok(val),
4490                            Err(_) => Ok(Value::None),
4491                        }
4492                    }
4493                    _ => Err(runtime_err_s("try_recv() expects a channel")),
4494                }
4495            }
4496            "await_all" => {
4497                if args.is_empty() {
4498                    return Err(runtime_err_s("await_all() expects a list of tasks"));
4499                }
4500                match &args[0] {
4501                    Value::List(tasks) => {
4502                        let mut results = Vec::with_capacity(tasks.len());
4503                        for task in tasks {
4504                            match task {
4505                                Value::Task(t) => {
4506                                    let rx = {
4507                                        let mut guard =
4508                                            t.receiver.lock().unwrap_or_else(|e| e.into_inner());
4509                                        guard.take()
4510                                    };
4511                                    match rx {
4512                                        Some(receiver) => match receiver.recv() {
4513                                            Ok(Ok(val)) => results.push(val),
4514                                            Ok(Err(e)) => return Err(runtime_err(e)),
4515                                            Err(_) => {
4516                                                return Err(runtime_err_s(
4517                                                    "Task channel disconnected",
4518                                                ));
4519                                            }
4520                                        },
4521                                        None => return Err(runtime_err_s("Task already awaited")),
4522                                    }
4523                                }
4524                                other => results.push(other.clone()),
4525                            }
4526                        }
4527                        Ok(Value::List(results))
4528                    }
4529                    _ => Err(runtime_err_s("await_all() expects a list")),
4530                }
4531            }
4532            "pmap" => {
4533                if args.len() < 2 {
4534                    return Err(runtime_err_s("pmap() expects a list and a function"));
4535                }
4536                let items = match &args[0] {
4537                    Value::List(items) => items.clone(),
4538                    _ => return Err(runtime_err_s("pmap() expects a list as first argument")),
4539                };
4540
4541                let env_scopes = self.env.scopes.clone();
4542                let method_table = self.method_table.clone();
4543
4544                match &args[1] {
4545                    Value::Function { params, body, .. } => {
4546                        let params = params.clone();
4547                        let body = body.clone();
4548                        let mut handles = Vec::with_capacity(items.len());
4549                        for item in items {
4550                            let params = params.clone();
4551                            let body = body.clone();
4552                            let env_scopes = env_scopes.clone();
4553                            let method_table = method_table.clone();
4554                            let handle = std::thread::spawn(move || {
4555                                let mut interp = Interpreter::new();
4556                                interp.env.scopes = env_scopes;
4557                                interp.method_table = method_table;
4558                                interp.env.push_scope();
4559                                if let Some(p) = params.first() {
4560                                    interp.env.set(p.name.clone(), item);
4561                                }
4562                                let mut result = Value::None;
4563                                for stmt in &body {
4564                                    match interp.exec_stmt(stmt) {
4565                                        Ok(Signal::Return(val)) => {
4566                                            result = val;
4567                                            break;
4568                                        }
4569                                        Ok(Signal::None) => {
4570                                            if let Some(val) = &interp.last_expr_value {
4571                                                result = val.clone();
4572                                            }
4573                                        }
4574                                        Ok(Signal::Throw(val)) => {
4575                                            interp.env.pop_scope();
4576                                            return Err(format!("{val}"));
4577                                        }
4578                                        Err(e) => {
4579                                            interp.env.pop_scope();
4580                                            return Err(format!("{e}"));
4581                                        }
4582                                        _ => {}
4583                                    }
4584                                }
4585                                interp.env.pop_scope();
4586                                Ok(result)
4587                            });
4588                            handles.push(handle);
4589                        }
4590                        let mut results = Vec::with_capacity(handles.len());
4591                        for handle in handles {
4592                            match handle.join() {
4593                                Ok(Ok(val)) => results.push(val),
4594                                Ok(Err(e)) => return Err(runtime_err(e)),
4595                                Err(_) => return Err(runtime_err_s("pmap() thread panicked")),
4596                            }
4597                        }
4598                        Ok(Value::List(results))
4599                    }
4600                    Value::Closure {
4601                        params,
4602                        body,
4603                        captured_env,
4604                    } => {
4605                        let params = params.clone();
4606                        let body = body.clone();
4607                        let captured_env = captured_env.clone();
4608                        let mut handles = Vec::with_capacity(items.len());
4609                        for item in items {
4610                            let params = params.clone();
4611                            let body = body.clone();
4612                            let captured_env = captured_env.clone();
4613                            let method_table = method_table.clone();
4614                            let handle = std::thread::spawn(move || {
4615                                let mut interp = Interpreter::new();
4616                                interp.env.scopes = captured_env;
4617                                interp.method_table = method_table;
4618                                interp.env.push_scope();
4619                                if let Some(p) = params.first() {
4620                                    interp.env.set(p.name.clone(), item);
4621                                }
4622                                let result = interp.eval_closure_body(&body);
4623                                interp.env.pop_scope();
4624                                result.map_err(|e| format!("{e}"))
4625                            });
4626                            handles.push(handle);
4627                        }
4628                        let mut results = Vec::with_capacity(handles.len());
4629                        for handle in handles {
4630                            match handle.join() {
4631                                Ok(Ok(val)) => results.push(val),
4632                                Ok(Err(e)) => return Err(runtime_err(e)),
4633                                Err(_) => return Err(runtime_err_s("pmap() thread panicked")),
4634                            }
4635                        }
4636                        Ok(Value::List(results))
4637                    }
4638                    _ => Err(runtime_err_s(
4639                        "pmap() expects a function as second argument",
4640                    )),
4641                }
4642            }
4643            "timeout" => {
4644                if args.len() < 2 {
4645                    return Err(runtime_err_s(
4646                        "timeout() expects a task and a duration in milliseconds",
4647                    ));
4648                }
4649                let ms = match &args[1] {
4650                    Value::Int(n) => *n as u64,
4651                    _ => return Err(runtime_err_s("timeout() expects an integer duration")),
4652                };
4653                match &args[0] {
4654                    Value::Task(task) => {
4655                        let rx = {
4656                            let mut guard = task.receiver.lock().unwrap_or_else(|e| e.into_inner());
4657                            guard.take()
4658                        };
4659                        match rx {
4660                            Some(receiver) => {
4661                                match receiver.recv_timeout(Duration::from_millis(ms)) {
4662                                    Ok(Ok(val)) => Ok(val),
4663                                    Ok(Err(e)) => Err(runtime_err(e)),
4664                                    Err(mpsc::RecvTimeoutError::Timeout) => {
4665                                        Err(runtime_err_s("Task timed out"))
4666                                    }
4667                                    Err(mpsc::RecvTimeoutError::Disconnected) => {
4668                                        Err(runtime_err_s("Task channel disconnected"))
4669                                    }
4670                                }
4671                            }
4672                            None => Err(runtime_err_s("Task already awaited")),
4673                        }
4674                    }
4675                    _ => Err(runtime_err_s("timeout() expects a task as first argument")),
4676                }
4677            }
4678
4679            // Phase 8: Iterators & Generators
4680            "next" => {
4681                if args.is_empty() {
4682                    return Err(runtime_err_s("next() expects a generator"));
4683                }
4684                match &args[0] {
4685                    Value::Generator(g) => self.interpreter_next(g),
4686                    _ => Err(runtime_err_s("next() expects a generator")),
4687                }
4688            }
4689            "is_generator" => {
4690                let val = args.first().unwrap_or(&Value::None);
4691                Ok(Value::Bool(matches!(val, Value::Generator(_))))
4692            }
4693            "iter" => {
4694                if args.is_empty() {
4695                    return Err(runtime_err_s("iter() expects a list"));
4696                }
4697                match &args[0] {
4698                    Value::List(items) => {
4699                        let g = TlGenerator::new(TlGeneratorKind::ListIter {
4700                            items: items.clone(),
4701                            index: Mutex::new(0),
4702                        });
4703                        Ok(Value::Generator(Arc::new(g)))
4704                    }
4705                    _ => Err(runtime_err_s("iter() expects a list")),
4706                }
4707            }
4708            "take" => {
4709                if args.len() < 2 {
4710                    return Err(runtime_err_s("take() expects a generator and a count"));
4711                }
4712                let g = match &args[0] {
4713                    Value::Generator(g) => g.clone(),
4714                    _ => return Err(runtime_err_s("take() expects a generator")),
4715                };
4716                let n = match &args[1] {
4717                    Value::Int(n) => *n as usize,
4718                    _ => return Err(runtime_err_s("take() expects an integer count")),
4719                };
4720                let gn = TlGenerator::new(TlGeneratorKind::Take {
4721                    source: g,
4722                    remaining: Mutex::new(n),
4723                });
4724                Ok(Value::Generator(Arc::new(gn)))
4725            }
4726            "skip" => {
4727                if args.len() < 2 {
4728                    return Err(runtime_err_s("skip() expects a generator and a count"));
4729                }
4730                let g = match &args[0] {
4731                    Value::Generator(g) => g.clone(),
4732                    _ => return Err(runtime_err_s("skip() expects a generator")),
4733                };
4734                let n = match &args[1] {
4735                    Value::Int(n) => *n as usize,
4736                    _ => return Err(runtime_err_s("skip() expects an integer count")),
4737                };
4738                let gn = TlGenerator::new(TlGeneratorKind::Skip {
4739                    source: g,
4740                    remaining: Mutex::new(n),
4741                });
4742                Ok(Value::Generator(Arc::new(gn)))
4743            }
4744            "gen_collect" => {
4745                if args.is_empty() {
4746                    return Err(runtime_err_s("gen_collect() expects a generator"));
4747                }
4748                match &args[0] {
4749                    Value::Generator(g) => {
4750                        let mut items = Vec::new();
4751                        loop {
4752                            let val = self.interpreter_next(g)?;
4753                            if matches!(val, Value::None) {
4754                                break;
4755                            }
4756                            items.push(val);
4757                        }
4758                        Ok(Value::List(items))
4759                    }
4760                    _ => Err(runtime_err_s("gen_collect() expects a generator")),
4761                }
4762            }
4763            "gen_map" => {
4764                if args.len() < 2 {
4765                    return Err(runtime_err_s(
4766                        "gen_map() expects a generator and a function",
4767                    ));
4768                }
4769                let g = match &args[0] {
4770                    Value::Generator(g) => g.clone(),
4771                    _ => return Err(runtime_err_s("gen_map() expects a generator")),
4772                };
4773                let gn = TlGenerator::new(TlGeneratorKind::Map {
4774                    source: g,
4775                    func: args[1].clone(),
4776                });
4777                Ok(Value::Generator(Arc::new(gn)))
4778            }
4779            "gen_filter" => {
4780                if args.len() < 2 {
4781                    return Err(runtime_err_s(
4782                        "gen_filter() expects a generator and a function",
4783                    ));
4784                }
4785                let g = match &args[0] {
4786                    Value::Generator(g) => g.clone(),
4787                    _ => return Err(runtime_err_s("gen_filter() expects a generator")),
4788                };
4789                let gn = TlGenerator::new(TlGeneratorKind::Filter {
4790                    source: g,
4791                    func: args[1].clone(),
4792                });
4793                Ok(Value::Generator(Arc::new(gn)))
4794            }
4795            "chain" => {
4796                if args.len() < 2 {
4797                    return Err(runtime_err_s("chain() expects two generators"));
4798                }
4799                let first = match &args[0] {
4800                    Value::Generator(g) => g.clone(),
4801                    _ => return Err(runtime_err_s("chain() expects generators")),
4802                };
4803                let second = match &args[1] {
4804                    Value::Generator(g) => g.clone(),
4805                    _ => return Err(runtime_err_s("chain() expects generators")),
4806                };
4807                let gn = TlGenerator::new(TlGeneratorKind::Chain {
4808                    first,
4809                    second,
4810                    on_second: Mutex::new(false),
4811                });
4812                Ok(Value::Generator(Arc::new(gn)))
4813            }
4814            "gen_zip" => {
4815                if args.len() < 2 {
4816                    return Err(runtime_err_s("gen_zip() expects two generators"));
4817                }
4818                let first = match &args[0] {
4819                    Value::Generator(g) => g.clone(),
4820                    _ => return Err(runtime_err_s("gen_zip() expects generators")),
4821                };
4822                let second = match &args[1] {
4823                    Value::Generator(g) => g.clone(),
4824                    _ => return Err(runtime_err_s("gen_zip() expects generators")),
4825                };
4826                let gn = TlGenerator::new(TlGeneratorKind::Zip { first, second });
4827                Ok(Value::Generator(Arc::new(gn)))
4828            }
4829            "gen_enumerate" => {
4830                if args.is_empty() {
4831                    return Err(runtime_err_s("gen_enumerate() expects a generator"));
4832                }
4833                let g = match &args[0] {
4834                    Value::Generator(g) => g.clone(),
4835                    _ => return Err(runtime_err_s("gen_enumerate() expects a generator")),
4836                };
4837                let gn = TlGenerator::new(TlGeneratorKind::Enumerate {
4838                    source: g,
4839                    index: Mutex::new(0),
4840                });
4841                Ok(Value::Generator(Arc::new(gn)))
4842            }
4843
4844            // Phase 10: Result builtins
4845            "Ok" => {
4846                let val = if args.is_empty() {
4847                    Value::None
4848                } else {
4849                    args[0].clone()
4850                };
4851                Ok(Value::EnumInstance {
4852                    type_name: "Result".to_string(),
4853                    variant: "Ok".to_string(),
4854                    fields: vec![val],
4855                })
4856            }
4857            "Err" => {
4858                let val = if args.is_empty() {
4859                    Value::String("error".to_string())
4860                } else {
4861                    args[0].clone()
4862                };
4863                Ok(Value::EnumInstance {
4864                    type_name: "Result".to_string(),
4865                    variant: "Err".to_string(),
4866                    fields: vec![val],
4867                })
4868            }
4869            "is_ok" => {
4870                if args.is_empty() {
4871                    return Err(runtime_err_s("is_ok() expects an argument"));
4872                }
4873                match &args[0] {
4874                    Value::EnumInstance {
4875                        type_name, variant, ..
4876                    } if type_name == "Result" => Ok(Value::Bool(variant == "Ok")),
4877                    _ => Ok(Value::Bool(false)),
4878                }
4879            }
4880            "is_err" => {
4881                if args.is_empty() {
4882                    return Err(runtime_err_s("is_err() expects an argument"));
4883                }
4884                match &args[0] {
4885                    Value::EnumInstance {
4886                        type_name, variant, ..
4887                    } if type_name == "Result" => Ok(Value::Bool(variant == "Err")),
4888                    _ => Ok(Value::Bool(false)),
4889                }
4890            }
4891            "unwrap" => {
4892                if args.is_empty() {
4893                    return Err(runtime_err_s("unwrap() expects an argument"));
4894                }
4895                match &args[0] {
4896                    Value::EnumInstance {
4897                        type_name,
4898                        variant,
4899                        fields,
4900                    } if type_name == "Result" => {
4901                        if variant == "Ok" && !fields.is_empty() {
4902                            Ok(fields[0].clone())
4903                        } else if variant == "Err" {
4904                            let msg = if fields.is_empty() {
4905                                "error".to_string()
4906                            } else {
4907                                format!("{}", fields[0])
4908                            };
4909                            Err(runtime_err(format!("unwrap() called on Err({msg})")))
4910                        } else {
4911                            Ok(Value::None)
4912                        }
4913                    }
4914                    Value::None => Err(runtime_err_s("unwrap() called on none")),
4915                    other => Ok(other.clone()),
4916                }
4917            }
4918            "set_from" => {
4919                if args.is_empty() {
4920                    return Ok(Value::Set(Vec::new()));
4921                }
4922                match &args[0] {
4923                    Value::List(items) => {
4924                        let mut result = Vec::new();
4925                        for item in items {
4926                            if !result.iter().any(|x| values_equal(x, item)) {
4927                                result.push(item.clone());
4928                            }
4929                        }
4930                        Ok(Value::Set(result))
4931                    }
4932                    _ => Err(runtime_err_s("set_from() expects a list")),
4933                }
4934            }
4935            "set_add" => {
4936                if args.len() < 2 {
4937                    return Err(runtime_err_s("set_add() expects (set, value)"));
4938                }
4939                match &args[0] {
4940                    Value::Set(items) => {
4941                        let val = &args[1];
4942                        let mut new_items = items.clone();
4943                        if !new_items.iter().any(|x| values_equal(x, val)) {
4944                            new_items.push(val.clone());
4945                        }
4946                        Ok(Value::Set(new_items))
4947                    }
4948                    _ => Err(runtime_err_s("set_add() expects a set as first argument")),
4949                }
4950            }
4951            "set_remove" => {
4952                if args.len() < 2 {
4953                    return Err(runtime_err_s("set_remove() expects (set, value)"));
4954                }
4955                match &args[0] {
4956                    Value::Set(items) => {
4957                        let val = &args[1];
4958                        let new_items: Vec<Value> = items
4959                            .iter()
4960                            .filter(|x| !values_equal(x, val))
4961                            .cloned()
4962                            .collect();
4963                        Ok(Value::Set(new_items))
4964                    }
4965                    _ => Err(runtime_err_s(
4966                        "set_remove() expects a set as first argument",
4967                    )),
4968                }
4969            }
4970            "set_contains" => {
4971                if args.len() < 2 {
4972                    return Err(runtime_err_s("set_contains() expects (set, value)"));
4973                }
4974                match &args[0] {
4975                    Value::Set(items) => {
4976                        let val = &args[1];
4977                        Ok(Value::Bool(items.iter().any(|x| values_equal(x, val))))
4978                    }
4979                    _ => Err(runtime_err_s(
4980                        "set_contains() expects a set as first argument",
4981                    )),
4982                }
4983            }
4984            "set_union" => {
4985                if args.len() < 2 {
4986                    return Err(runtime_err_s("set_union() expects (set, set)"));
4987                }
4988                match (&args[0], &args[1]) {
4989                    (Value::Set(a), Value::Set(b)) => {
4990                        let mut result = a.clone();
4991                        for item in b {
4992                            if !result.iter().any(|x| values_equal(x, item)) {
4993                                result.push(item.clone());
4994                            }
4995                        }
4996                        Ok(Value::Set(result))
4997                    }
4998                    _ => Err(runtime_err_s("set_union() expects two sets")),
4999                }
5000            }
5001            "set_intersection" => {
5002                if args.len() < 2 {
5003                    return Err(runtime_err_s("set_intersection() expects (set, set)"));
5004                }
5005                match (&args[0], &args[1]) {
5006                    (Value::Set(a), Value::Set(b)) => {
5007                        let result: Vec<Value> = a
5008                            .iter()
5009                            .filter(|x| b.iter().any(|y| values_equal(x, y)))
5010                            .cloned()
5011                            .collect();
5012                        Ok(Value::Set(result))
5013                    }
5014                    _ => Err(runtime_err_s("set_intersection() expects two sets")),
5015                }
5016            }
5017            "set_difference" => {
5018                if args.len() < 2 {
5019                    return Err(runtime_err_s("set_difference() expects (set, set)"));
5020                }
5021                match (&args[0], &args[1]) {
5022                    (Value::Set(a), Value::Set(b)) => {
5023                        let result: Vec<Value> = a
5024                            .iter()
5025                            .filter(|x| !b.iter().any(|y| values_equal(x, y)))
5026                            .cloned()
5027                            .collect();
5028                        Ok(Value::Set(result))
5029                    }
5030                    _ => Err(runtime_err_s("set_difference() expects two sets")),
5031                }
5032            }
5033
5034            // ── Phase 15: Data Quality & Connectors ──
5035            "fill_null" => {
5036                if args.len() < 2 {
5037                    return Err(runtime_err_s(
5038                        "fill_null() expects (table, column, [strategy], [value])",
5039                    ));
5040                }
5041                let df = match &args[0] {
5042                    Value::Table(t) => t.df.clone(),
5043                    _ => return Err(runtime_err_s("fill_null() first arg must be a table")),
5044                };
5045                let column = match &args[1] {
5046                    Value::String(s) => s.clone(),
5047                    _ => return Err(runtime_err_s("fill_null() column must be a string")),
5048                };
5049                let strategy = if args.len() > 2 {
5050                    match &args[2] {
5051                        Value::String(s) => s.clone(),
5052                        _ => "value".to_string(),
5053                    }
5054                } else {
5055                    "value".to_string()
5056                };
5057                let fill_value = if args.len() > 3 {
5058                    match &args[3] {
5059                        Value::Int(n) => Some(*n as f64),
5060                        Value::Float(f) => Some(*f),
5061                        _ => None,
5062                    }
5063                } else if args.len() > 2 && strategy == "value" {
5064                    match &args[2] {
5065                        Value::Int(n) => {
5066                            let r = self
5067                                .engine()
5068                                .fill_null(df, &column, "value", Some(*n as f64))
5069                                .map_err(runtime_err)?;
5070                            return Ok(Value::Table(TlTable { df: r }));
5071                        }
5072                        Value::Float(f) => {
5073                            let r = self
5074                                .engine()
5075                                .fill_null(df, &column, "value", Some(*f))
5076                                .map_err(runtime_err)?;
5077                            return Ok(Value::Table(TlTable { df: r }));
5078                        }
5079                        _ => None,
5080                    }
5081                } else {
5082                    None
5083                };
5084                let result = self
5085                    .engine()
5086                    .fill_null(df, &column, &strategy, fill_value)
5087                    .map_err(runtime_err)?;
5088                Ok(Value::Table(TlTable { df: result }))
5089            }
5090            "drop_null" => {
5091                if args.len() < 2 {
5092                    return Err(runtime_err_s("drop_null() expects (table, column)"));
5093                }
5094                let df = match &args[0] {
5095                    Value::Table(t) => t.df.clone(),
5096                    _ => return Err(runtime_err_s("drop_null() first arg must be a table")),
5097                };
5098                let column = match &args[1] {
5099                    Value::String(s) => s.clone(),
5100                    _ => return Err(runtime_err_s("drop_null() column must be a string")),
5101                };
5102                let result = self.engine().drop_null(df, &column).map_err(runtime_err)?;
5103                Ok(Value::Table(TlTable { df: result }))
5104            }
5105            "dedup" => {
5106                if args.is_empty() {
5107                    return Err(runtime_err_s("dedup() expects (table, [columns...])"));
5108                }
5109                let df = match &args[0] {
5110                    Value::Table(t) => t.df.clone(),
5111                    _ => return Err(runtime_err_s("dedup() first arg must be a table")),
5112                };
5113                let columns: Vec<String> = args[1..]
5114                    .iter()
5115                    .filter_map(|a| {
5116                        if let Value::String(s) = a {
5117                            Some(s.clone())
5118                        } else {
5119                            None
5120                        }
5121                    })
5122                    .collect();
5123                let result = self.engine().dedup(df, &columns).map_err(runtime_err)?;
5124                Ok(Value::Table(TlTable { df: result }))
5125            }
5126            "clamp" => {
5127                if args.len() < 4 {
5128                    return Err(runtime_err_s("clamp() expects (table, column, min, max)"));
5129                }
5130                let df = match &args[0] {
5131                    Value::Table(t) => t.df.clone(),
5132                    _ => return Err(runtime_err_s("clamp() first arg must be a table")),
5133                };
5134                let column = match &args[1] {
5135                    Value::String(s) => s.clone(),
5136                    _ => return Err(runtime_err_s("clamp() column must be a string")),
5137                };
5138                let min_val = match &args[2] {
5139                    Value::Int(n) => *n as f64,
5140                    Value::Float(f) => *f,
5141                    _ => return Err(runtime_err_s("clamp() min must be a number")),
5142                };
5143                let max_val = match &args[3] {
5144                    Value::Int(n) => *n as f64,
5145                    Value::Float(f) => *f,
5146                    _ => return Err(runtime_err_s("clamp() max must be a number")),
5147                };
5148                let result = self
5149                    .engine()
5150                    .clamp(df, &column, min_val, max_val)
5151                    .map_err(runtime_err)?;
5152                Ok(Value::Table(TlTable { df: result }))
5153            }
5154            "data_profile" => {
5155                if args.is_empty() {
5156                    return Err(runtime_err_s("data_profile() expects (table)"));
5157                }
5158                let df = match &args[0] {
5159                    Value::Table(t) => t.df.clone(),
5160                    _ => return Err(runtime_err_s("data_profile() arg must be a table")),
5161                };
5162                let result = self.engine().data_profile(df).map_err(runtime_err)?;
5163                Ok(Value::Table(TlTable { df: result }))
5164            }
5165            "row_count" => {
5166                if args.is_empty() {
5167                    return Err(runtime_err_s("row_count() expects (table)"));
5168                }
5169                let df = match &args[0] {
5170                    Value::Table(t) => t.df.clone(),
5171                    _ => return Err(runtime_err_s("row_count() arg must be a table")),
5172                };
5173                let count = self.engine().row_count(df).map_err(runtime_err)?;
5174                Ok(Value::Int(count))
5175            }
5176            "null_rate" => {
5177                if args.len() < 2 {
5178                    return Err(runtime_err_s("null_rate() expects (table, column)"));
5179                }
5180                let df = match &args[0] {
5181                    Value::Table(t) => t.df.clone(),
5182                    _ => return Err(runtime_err_s("null_rate() first arg must be a table")),
5183                };
5184                let column = match &args[1] {
5185                    Value::String(s) => s.clone(),
5186                    _ => return Err(runtime_err_s("null_rate() column must be a string")),
5187                };
5188                let rate = self.engine().null_rate(df, &column).map_err(runtime_err)?;
5189                Ok(Value::Float(rate))
5190            }
5191            "is_unique" => {
5192                if args.len() < 2 {
5193                    return Err(runtime_err_s("is_unique() expects (table, column)"));
5194                }
5195                let df = match &args[0] {
5196                    Value::Table(t) => t.df.clone(),
5197                    _ => return Err(runtime_err_s("is_unique() first arg must be a table")),
5198                };
5199                let column = match &args[1] {
5200                    Value::String(s) => s.clone(),
5201                    _ => return Err(runtime_err_s("is_unique() column must be a string")),
5202                };
5203                let unique = self.engine().is_unique(df, &column).map_err(runtime_err)?;
5204                Ok(Value::Bool(unique))
5205            }
5206            "is_email" => {
5207                if args.is_empty() {
5208                    return Err(runtime_err_s("is_email() expects 1 argument"));
5209                }
5210                let s = match &args[0] {
5211                    Value::String(s) => s.clone(),
5212                    _ => return Err(runtime_err_s("is_email() arg must be a string")),
5213                };
5214                Ok(Value::Bool(tl_data::validate::is_email(&s)))
5215            }
5216            "is_url" => {
5217                if args.is_empty() {
5218                    return Err(runtime_err_s("is_url() expects 1 argument"));
5219                }
5220                let s = match &args[0] {
5221                    Value::String(s) => s.clone(),
5222                    _ => return Err(runtime_err_s("is_url() arg must be a string")),
5223                };
5224                Ok(Value::Bool(tl_data::validate::is_url(&s)))
5225            }
5226            "is_phone" => {
5227                if args.is_empty() {
5228                    return Err(runtime_err_s("is_phone() expects 1 argument"));
5229                }
5230                let s = match &args[0] {
5231                    Value::String(s) => s.clone(),
5232                    _ => return Err(runtime_err_s("is_phone() arg must be a string")),
5233                };
5234                Ok(Value::Bool(tl_data::validate::is_phone(&s)))
5235            }
5236            "is_between" => {
5237                if args.len() < 3 {
5238                    return Err(runtime_err_s("is_between() expects (value, low, high)"));
5239                }
5240                let val = match &args[0] {
5241                    Value::Int(n) => *n as f64,
5242                    Value::Float(f) => *f,
5243                    _ => return Err(runtime_err_s("is_between() value must be a number")),
5244                };
5245                let low = match &args[1] {
5246                    Value::Int(n) => *n as f64,
5247                    Value::Float(f) => *f,
5248                    _ => return Err(runtime_err_s("is_between() low must be a number")),
5249                };
5250                let high = match &args[2] {
5251                    Value::Int(n) => *n as f64,
5252                    Value::Float(f) => *f,
5253                    _ => return Err(runtime_err_s("is_between() high must be a number")),
5254                };
5255                Ok(Value::Bool(tl_data::validate::is_between(val, low, high)))
5256            }
5257            "levenshtein" => {
5258                if args.len() < 2 {
5259                    return Err(runtime_err_s("levenshtein() expects (str_a, str_b)"));
5260                }
5261                let a = match &args[0] {
5262                    Value::String(s) => s.clone(),
5263                    _ => return Err(runtime_err_s("levenshtein() args must be strings")),
5264                };
5265                let b = match &args[1] {
5266                    Value::String(s) => s.clone(),
5267                    _ => return Err(runtime_err_s("levenshtein() args must be strings")),
5268                };
5269                Ok(Value::Int(tl_data::validate::levenshtein(&a, &b) as i64))
5270            }
5271            "soundex" => {
5272                if args.is_empty() {
5273                    return Err(runtime_err_s("soundex() expects 1 argument"));
5274                }
5275                let s = match &args[0] {
5276                    Value::String(s) => s.clone(),
5277                    _ => return Err(runtime_err_s("soundex() arg must be a string")),
5278                };
5279                Ok(Value::String(tl_data::validate::soundex(&s)))
5280            }
5281            "read_mysql" => {
5282                #[cfg(feature = "mysql")]
5283                {
5284                    if args.len() < 2 {
5285                        return Err(runtime_err_s("read_mysql() expects (conn_str, query)"));
5286                    }
5287                    let conn_str = match &args[0] {
5288                        Value::String(s) => s.clone(),
5289                        _ => return Err(runtime_err_s("read_mysql() conn_str must be a string")),
5290                    };
5291                    let query = match &args[1] {
5292                        Value::String(s) => s.clone(),
5293                        _ => return Err(runtime_err_s("read_mysql() query must be a string")),
5294                    };
5295                    let df = self
5296                        .engine()
5297                        .read_mysql(&conn_str, &query)
5298                        .map_err(runtime_err)?;
5299                    Ok(Value::Table(TlTable { df }))
5300                }
5301                #[cfg(not(feature = "mysql"))]
5302                Err(runtime_err_s("read_mysql() requires the 'mysql' feature"))
5303            }
5304            "read_sqlite" => {
5305                #[cfg(feature = "sqlite")]
5306                {
5307                    if args.len() < 2 {
5308                        return Err(runtime_err_s("read_sqlite() expects (db_path, query)"));
5309                    }
5310                    let db_path = match &args[0] {
5311                        Value::String(s) => s.clone(),
5312                        _ => return Err(runtime_err_s("read_sqlite() db_path must be a string")),
5313                    };
5314                    let query = match &args[1] {
5315                        Value::String(s) => s.clone(),
5316                        _ => return Err(runtime_err_s("read_sqlite() query must be a string")),
5317                    };
5318                    let df = self
5319                        .engine()
5320                        .read_sqlite(&db_path, &query)
5321                        .map_err(runtime_err)?;
5322                    Ok(Value::Table(TlTable { df }))
5323                }
5324                #[cfg(not(feature = "sqlite"))]
5325                Err(runtime_err_s("read_sqlite() requires the 'sqlite' feature"))
5326            }
5327            "write_sqlite" => {
5328                #[cfg(feature = "sqlite")]
5329                {
5330                    if args.len() < 3 {
5331                        return Err(runtime_err_s(
5332                            "write_sqlite() expects (table, db_path, table_name)",
5333                        ));
5334                    }
5335                    let df = match &args[0] {
5336                        Value::Table(t) => t.df.clone(),
5337                        _ => return Err(runtime_err_s("write_sqlite() first arg must be a table")),
5338                    };
5339                    let db_path = match &args[1] {
5340                        Value::String(s) => s.clone(),
5341                        _ => return Err(runtime_err_s("write_sqlite() db_path must be a string")),
5342                    };
5343                    let table_name = match &args[2] {
5344                        Value::String(s) => s.clone(),
5345                        _ => {
5346                            return Err(runtime_err_s(
5347                                "write_sqlite() table_name must be a string",
5348                            ));
5349                        }
5350                    };
5351                    self.engine()
5352                        .write_sqlite(df, &db_path, &table_name)
5353                        .map_err(runtime_err)?;
5354                    Ok(Value::None)
5355                }
5356                #[cfg(not(feature = "sqlite"))]
5357                Err(runtime_err_s(
5358                    "write_sqlite() requires the 'sqlite' feature",
5359                ))
5360            }
5361            "duckdb" | "read_duckdb" => {
5362                #[cfg(feature = "duckdb")]
5363                {
5364                    if args.len() < 2 {
5365                        return Err(runtime_err_s("duckdb() expects (db_path, query)"));
5366                    }
5367                    let db_path = match &args[0] {
5368                        Value::String(s) => s.clone(),
5369                        _ => return Err(runtime_err_s("duckdb() db_path must be a string")),
5370                    };
5371                    let query = match &args[1] {
5372                        Value::String(s) => s.clone(),
5373                        _ => return Err(runtime_err_s("duckdb() query must be a string")),
5374                    };
5375                    let df = self
5376                        .engine()
5377                        .read_duckdb(&db_path, &query)
5378                        .map_err(runtime_err)?;
5379                    Ok(Value::Table(TlTable { df }))
5380                }
5381                #[cfg(not(feature = "duckdb"))]
5382                Err(runtime_err_s("duckdb() requires the 'duckdb' feature"))
5383            }
5384            "write_duckdb" => {
5385                #[cfg(feature = "duckdb")]
5386                {
5387                    if args.len() < 3 {
5388                        return Err(runtime_err_s(
5389                            "write_duckdb() expects (table, db_path, table_name)",
5390                        ));
5391                    }
5392                    let df = match &args[0] {
5393                        Value::Table(t) => t.df.clone(),
5394                        _ => return Err(runtime_err_s("write_duckdb() first arg must be a table")),
5395                    };
5396                    let db_path = match &args[1] {
5397                        Value::String(s) => s.clone(),
5398                        _ => return Err(runtime_err_s("write_duckdb() db_path must be a string")),
5399                    };
5400                    let table_name = match &args[2] {
5401                        Value::String(s) => s.clone(),
5402                        _ => {
5403                            return Err(runtime_err_s(
5404                                "write_duckdb() table_name must be a string",
5405                            ));
5406                        }
5407                    };
5408                    self.engine()
5409                        .write_duckdb(df, &db_path, &table_name)
5410                        .map_err(runtime_err)?;
5411                    Ok(Value::None)
5412                }
5413                #[cfg(not(feature = "duckdb"))]
5414                Err(runtime_err_s(
5415                    "write_duckdb() requires the 'duckdb' feature",
5416                ))
5417            }
5418            "redshift" | "read_redshift" => {
5419                if args.len() < 2 {
5420                    return Err(runtime_err_s("redshift() expects (conn_str, query)"));
5421                }
5422                let conn_str = match &args[0] {
5423                    Value::String(s) => resolve_tl_config_connection_interp(s),
5424                    _ => return Err(runtime_err_s("redshift() conn_str must be a string")),
5425                };
5426                let query = match &args[1] {
5427                    Value::String(s) => s.clone(),
5428                    _ => return Err(runtime_err_s("redshift() query must be a string")),
5429                };
5430                let df = self
5431                    .engine()
5432                    .read_redshift(&conn_str, &query)
5433                    .map_err(runtime_err)?;
5434                Ok(Value::Table(TlTable { df }))
5435            }
5436            "mssql" | "read_mssql" => {
5437                #[cfg(feature = "mssql")]
5438                {
5439                    if args.len() < 2 {
5440                        return Err(runtime_err_s("mssql() expects (conn_str, query)"));
5441                    }
5442                    let conn_str = match &args[0] {
5443                        Value::String(s) => resolve_tl_config_connection_interp(s),
5444                        _ => return Err(runtime_err_s("mssql() conn_str must be a string")),
5445                    };
5446                    let query = match &args[1] {
5447                        Value::String(s) => s.clone(),
5448                        _ => return Err(runtime_err_s("mssql() query must be a string")),
5449                    };
5450                    let df = self
5451                        .engine()
5452                        .read_mssql(&conn_str, &query)
5453                        .map_err(runtime_err)?;
5454                    Ok(Value::Table(TlTable { df }))
5455                }
5456                #[cfg(not(feature = "mssql"))]
5457                Err(runtime_err_s("mssql() requires the 'mssql' feature"))
5458            }
5459            "snowflake" | "read_snowflake" => {
5460                #[cfg(feature = "snowflake")]
5461                {
5462                    if args.len() < 2 {
5463                        return Err(runtime_err_s("snowflake() expects (config, query)"));
5464                    }
5465                    let config = match &args[0] {
5466                        Value::String(s) => resolve_tl_config_connection_interp(s),
5467                        _ => return Err(runtime_err_s("snowflake() config must be a string")),
5468                    };
5469                    let query = match &args[1] {
5470                        Value::String(s) => s.clone(),
5471                        _ => return Err(runtime_err_s("snowflake() query must be a string")),
5472                    };
5473                    let df = self
5474                        .engine()
5475                        .read_snowflake(&config, &query)
5476                        .map_err(runtime_err)?;
5477                    Ok(Value::Table(TlTable { df }))
5478                }
5479                #[cfg(not(feature = "snowflake"))]
5480                Err(runtime_err_s(
5481                    "snowflake() requires the 'snowflake' feature",
5482                ))
5483            }
5484            "bigquery" | "read_bigquery" => {
5485                #[cfg(feature = "bigquery")]
5486                {
5487                    if args.len() < 2 {
5488                        return Err(runtime_err_s("bigquery() expects (config, query)"));
5489                    }
5490                    let config = match &args[0] {
5491                        Value::String(s) => resolve_tl_config_connection_interp(s),
5492                        _ => return Err(runtime_err_s("bigquery() config must be a string")),
5493                    };
5494                    let query = match &args[1] {
5495                        Value::String(s) => s.clone(),
5496                        _ => return Err(runtime_err_s("bigquery() query must be a string")),
5497                    };
5498                    let df = self
5499                        .engine()
5500                        .read_bigquery(&config, &query)
5501                        .map_err(runtime_err)?;
5502                    Ok(Value::Table(TlTable { df }))
5503                }
5504                #[cfg(not(feature = "bigquery"))]
5505                Err(runtime_err_s("bigquery() requires the 'bigquery' feature"))
5506            }
5507            "databricks" | "read_databricks" => {
5508                #[cfg(feature = "databricks")]
5509                {
5510                    if args.len() < 2 {
5511                        return Err(runtime_err_s("databricks() expects (config, query)"));
5512                    }
5513                    let config = match &args[0] {
5514                        Value::String(s) => resolve_tl_config_connection_interp(s),
5515                        _ => return Err(runtime_err_s("databricks() config must be a string")),
5516                    };
5517                    let query = match &args[1] {
5518                        Value::String(s) => s.clone(),
5519                        _ => return Err(runtime_err_s("databricks() query must be a string")),
5520                    };
5521                    let df = self
5522                        .engine()
5523                        .read_databricks(&config, &query)
5524                        .map_err(runtime_err)?;
5525                    Ok(Value::Table(TlTable { df }))
5526                }
5527                #[cfg(not(feature = "databricks"))]
5528                Err(runtime_err_s(
5529                    "databricks() requires the 'databricks' feature",
5530                ))
5531            }
5532            "clickhouse" | "read_clickhouse" => {
5533                #[cfg(feature = "clickhouse")]
5534                {
5535                    if args.len() < 2 {
5536                        return Err(runtime_err_s("clickhouse() expects (url, query)"));
5537                    }
5538                    let url = match &args[0] {
5539                        Value::String(s) => resolve_tl_config_connection_interp(s),
5540                        _ => return Err(runtime_err_s("clickhouse() url must be a string")),
5541                    };
5542                    let query = match &args[1] {
5543                        Value::String(s) => s.clone(),
5544                        _ => return Err(runtime_err_s("clickhouse() query must be a string")),
5545                    };
5546                    let df = self
5547                        .engine()
5548                        .read_clickhouse(&url, &query)
5549                        .map_err(runtime_err)?;
5550                    Ok(Value::Table(TlTable { df }))
5551                }
5552                #[cfg(not(feature = "clickhouse"))]
5553                Err(runtime_err_s(
5554                    "clickhouse() requires the 'clickhouse' feature",
5555                ))
5556            }
5557            "mongo" | "read_mongo" | "read_mongodb" => {
5558                #[cfg(feature = "mongodb")]
5559                {
5560                    if args.len() < 4 {
5561                        return Err(runtime_err_s(
5562                            "mongo() expects (conn_str, database, collection, filter_json)",
5563                        ));
5564                    }
5565                    let conn_str = match &args[0] {
5566                        Value::String(s) => resolve_tl_config_connection_interp(s),
5567                        _ => return Err(runtime_err_s("mongo() conn_str must be a string")),
5568                    };
5569                    let database = match &args[1] {
5570                        Value::String(s) => s.clone(),
5571                        _ => return Err(runtime_err_s("mongo() database must be a string")),
5572                    };
5573                    let collection = match &args[2] {
5574                        Value::String(s) => s.clone(),
5575                        _ => return Err(runtime_err_s("mongo() collection must be a string")),
5576                    };
5577                    let filter_json = match &args[3] {
5578                        Value::String(s) => s.clone(),
5579                        _ => return Err(runtime_err_s("mongo() filter must be a string")),
5580                    };
5581                    let df = self
5582                        .engine()
5583                        .read_mongo(&conn_str, &database, &collection, &filter_json)
5584                        .map_err(runtime_err)?;
5585                    Ok(Value::Table(TlTable { df }))
5586                }
5587                #[cfg(not(feature = "mongodb"))]
5588                Err(runtime_err_s("mongo() requires the 'mongodb' feature"))
5589            }
5590            "sftp_download" => {
5591                #[cfg(feature = "sftp")]
5592                {
5593                    if args.len() < 3 {
5594                        return Err(runtime_err_s(
5595                            "sftp_download() expects (config, remote_path, local_path)",
5596                        ));
5597                    }
5598                    let config = match &args[0] {
5599                        Value::String(s) => resolve_tl_config_connection_interp(s),
5600                        _ => return Err(runtime_err_s("sftp_download() config must be a string")),
5601                    };
5602                    let remote = match &args[1] {
5603                        Value::String(s) => s.clone(),
5604                        _ => {
5605                            return Err(runtime_err_s(
5606                                "sftp_download() remote_path must be a string",
5607                            ));
5608                        }
5609                    };
5610                    let local = match &args[2] {
5611                        Value::String(s) => s.clone(),
5612                        _ => {
5613                            return Err(runtime_err_s(
5614                                "sftp_download() local_path must be a string",
5615                            ));
5616                        }
5617                    };
5618                    let result = self
5619                        .engine()
5620                        .sftp_download(&config, &remote, &local)
5621                        .map_err(runtime_err)?;
5622                    Ok(Value::String(result))
5623                }
5624                #[cfg(not(feature = "sftp"))]
5625                Err(runtime_err_s("sftp_download() requires the 'sftp' feature"))
5626            }
5627            "sftp_upload" => {
5628                #[cfg(feature = "sftp")]
5629                {
5630                    if args.len() < 3 {
5631                        return Err(runtime_err_s(
5632                            "sftp_upload() expects (config, local_path, remote_path)",
5633                        ));
5634                    }
5635                    let config = match &args[0] {
5636                        Value::String(s) => resolve_tl_config_connection_interp(s),
5637                        _ => return Err(runtime_err_s("sftp_upload() config must be a string")),
5638                    };
5639                    let local = match &args[1] {
5640                        Value::String(s) => s.clone(),
5641                        _ => {
5642                            return Err(runtime_err_s("sftp_upload() local_path must be a string"));
5643                        }
5644                    };
5645                    let remote = match &args[2] {
5646                        Value::String(s) => s.clone(),
5647                        _ => {
5648                            return Err(runtime_err_s(
5649                                "sftp_upload() remote_path must be a string",
5650                            ));
5651                        }
5652                    };
5653                    let result = self
5654                        .engine()
5655                        .sftp_upload(&config, &local, &remote)
5656                        .map_err(runtime_err)?;
5657                    Ok(Value::String(result))
5658                }
5659                #[cfg(not(feature = "sftp"))]
5660                Err(runtime_err_s("sftp_upload() requires the 'sftp' feature"))
5661            }
5662            "sftp_list" | "sftp_ls" => {
5663                #[cfg(feature = "sftp")]
5664                {
5665                    if args.len() < 2 {
5666                        return Err(runtime_err_s("sftp_list() expects (config, remote_path)"));
5667                    }
5668                    let config = match &args[0] {
5669                        Value::String(s) => resolve_tl_config_connection_interp(s),
5670                        _ => return Err(runtime_err_s("sftp_list() config must be a string")),
5671                    };
5672                    let remote = match &args[1] {
5673                        Value::String(s) => s.clone(),
5674                        _ => return Err(runtime_err_s("sftp_list() remote_path must be a string")),
5675                    };
5676                    let df = self
5677                        .engine()
5678                        .sftp_list(&config, &remote)
5679                        .map_err(runtime_err)?;
5680                    Ok(Value::Table(TlTable { df }))
5681                }
5682                #[cfg(not(feature = "sftp"))]
5683                Err(runtime_err_s("sftp_list() requires the 'sftp' feature"))
5684            }
5685            "sftp_read_csv" => {
5686                #[cfg(feature = "sftp")]
5687                {
5688                    if args.len() < 2 {
5689                        return Err(runtime_err_s(
5690                            "sftp_read_csv() expects (config, remote_path)",
5691                        ));
5692                    }
5693                    let config = match &args[0] {
5694                        Value::String(s) => resolve_tl_config_connection_interp(s),
5695                        _ => return Err(runtime_err_s("sftp_read_csv() config must be a string")),
5696                    };
5697                    let remote = match &args[1] {
5698                        Value::String(s) => s.clone(),
5699                        _ => {
5700                            return Err(runtime_err_s(
5701                                "sftp_read_csv() remote_path must be a string",
5702                            ));
5703                        }
5704                    };
5705                    let df = self
5706                        .engine()
5707                        .sftp_read_csv(&config, &remote)
5708                        .map_err(runtime_err)?;
5709                    Ok(Value::Table(TlTable { df }))
5710                }
5711                #[cfg(not(feature = "sftp"))]
5712                Err(runtime_err_s("sftp_read_csv() requires the 'sftp' feature"))
5713            }
5714            "sftp_read_parquet" => {
5715                #[cfg(feature = "sftp")]
5716                {
5717                    if args.len() < 2 {
5718                        return Err(runtime_err_s(
5719                            "sftp_read_parquet() expects (config, remote_path)",
5720                        ));
5721                    }
5722                    let config = match &args[0] {
5723                        Value::String(s) => resolve_tl_config_connection_interp(s),
5724                        _ => {
5725                            return Err(runtime_err_s(
5726                                "sftp_read_parquet() config must be a string",
5727                            ));
5728                        }
5729                    };
5730                    let remote = match &args[1] {
5731                        Value::String(s) => s.clone(),
5732                        _ => {
5733                            return Err(runtime_err_s(
5734                                "sftp_read_parquet() remote_path must be a string",
5735                            ));
5736                        }
5737                    };
5738                    let df = self
5739                        .engine()
5740                        .sftp_read_parquet(&config, &remote)
5741                        .map_err(runtime_err)?;
5742                    Ok(Value::Table(TlTable { df }))
5743                }
5744                #[cfg(not(feature = "sftp"))]
5745                Err(runtime_err_s(
5746                    "sftp_read_parquet() requires the 'sftp' feature",
5747                ))
5748            }
5749            "redis_connect" => {
5750                #[cfg(feature = "redis")]
5751                {
5752                    if args.is_empty() {
5753                        return Err(runtime_err_s("redis_connect() expects (url)"));
5754                    }
5755                    let url = match &args[0] {
5756                        Value::String(s) => s.clone(),
5757                        _ => return Err(runtime_err_s("redis_connect() url must be a string")),
5758                    };
5759                    let result = tl_data::redis_conn::redis_connect(&url).map_err(runtime_err)?;
5760                    Ok(Value::String(result))
5761                }
5762                #[cfg(not(feature = "redis"))]
5763                Err(runtime_err_s(
5764                    "redis_connect() requires the 'redis' feature",
5765                ))
5766            }
5767            "redis_get" => {
5768                #[cfg(feature = "redis")]
5769                {
5770                    if args.len() < 2 {
5771                        return Err(runtime_err_s("redis_get() expects (url, key)"));
5772                    }
5773                    let url = match &args[0] {
5774                        Value::String(s) => s.clone(),
5775                        _ => return Err(runtime_err_s("redis_get() url must be a string")),
5776                    };
5777                    let key = match &args[1] {
5778                        Value::String(s) => s.clone(),
5779                        _ => return Err(runtime_err_s("redis_get() key must be a string")),
5780                    };
5781                    match tl_data::redis_conn::redis_get(&url, &key).map_err(runtime_err)? {
5782                        Some(v) => Ok(Value::String(v)),
5783                        None => Ok(Value::None),
5784                    }
5785                }
5786                #[cfg(not(feature = "redis"))]
5787                Err(runtime_err_s("redis_get() requires the 'redis' feature"))
5788            }
5789            "redis_set" => {
5790                #[cfg(feature = "redis")]
5791                {
5792                    if args.len() < 3 {
5793                        return Err(runtime_err_s("redis_set() expects (url, key, value)"));
5794                    }
5795                    let url = match &args[0] {
5796                        Value::String(s) => s.clone(),
5797                        _ => return Err(runtime_err_s("redis_set() url must be a string")),
5798                    };
5799                    let key = match &args[1] {
5800                        Value::String(s) => s.clone(),
5801                        _ => return Err(runtime_err_s("redis_set() key must be a string")),
5802                    };
5803                    let value = match &args[2] {
5804                        Value::String(s) => s.clone(),
5805                        _ => format!("{}", &args[2]),
5806                    };
5807                    tl_data::redis_conn::redis_set(&url, &key, &value).map_err(runtime_err)?;
5808                    Ok(Value::None)
5809                }
5810                #[cfg(not(feature = "redis"))]
5811                Err(runtime_err_s("redis_set() requires the 'redis' feature"))
5812            }
5813            "redis_del" => {
5814                #[cfg(feature = "redis")]
5815                {
5816                    if args.len() < 2 {
5817                        return Err(runtime_err_s("redis_del() expects (url, key)"));
5818                    }
5819                    let url = match &args[0] {
5820                        Value::String(s) => s.clone(),
5821                        _ => return Err(runtime_err_s("redis_del() url must be a string")),
5822                    };
5823                    let key = match &args[1] {
5824                        Value::String(s) => s.clone(),
5825                        _ => return Err(runtime_err_s("redis_del() key must be a string")),
5826                    };
5827                    let deleted =
5828                        tl_data::redis_conn::redis_del(&url, &key).map_err(runtime_err)?;
5829                    Ok(Value::Bool(deleted))
5830                }
5831                #[cfg(not(feature = "redis"))]
5832                Err(runtime_err_s("redis_del() requires the 'redis' feature"))
5833            }
5834            "graphql_query" => {
5835                if args.len() < 2 {
5836                    return Err(runtime_err_s(
5837                        "graphql_query() expects (endpoint, query, [variables])",
5838                    ));
5839                }
5840                let endpoint = match &args[0] {
5841                    Value::String(s) => s.clone(),
5842                    _ => return Err(runtime_err_s("graphql_query() endpoint must be a string")),
5843                };
5844                let query = match &args[1] {
5845                    Value::String(s) => s.clone(),
5846                    _ => return Err(runtime_err_s("graphql_query() query must be a string")),
5847                };
5848                let variables = if args.len() > 2 {
5849                    value_to_json(&args[2])
5850                } else {
5851                    serde_json::Value::Null
5852                };
5853                let mut body = serde_json::Map::new();
5854                body.insert("query".to_string(), serde_json::Value::String(query));
5855                if !variables.is_null() {
5856                    body.insert("variables".to_string(), variables);
5857                }
5858                let client = reqwest::blocking::Client::new();
5859                let resp = client
5860                    .post(&endpoint)
5861                    .header("Content-Type", "application/json")
5862                    .json(&body)
5863                    .send()
5864                    .map_err(|e| runtime_err(format!("graphql_query() request error: {e}")))?;
5865                let text = resp
5866                    .text()
5867                    .map_err(|e| runtime_err(format!("graphql_query() response error: {e}")))?;
5868                let json: serde_json::Value = serde_json::from_str(&text)
5869                    .map_err(|e| runtime_err(format!("graphql_query() JSON parse error: {e}")))?;
5870                Ok(json_to_value(&json))
5871            }
5872            "register_s3" => {
5873                #[cfg(feature = "s3")]
5874                {
5875                    if args.len() < 2 {
5876                        return Err(runtime_err_s(
5877                            "register_s3() expects (bucket, region, [access_key], [secret_key], [endpoint])",
5878                        ));
5879                    }
5880                    let bucket = match &args[0] {
5881                        Value::String(s) => s.clone(),
5882                        _ => return Err(runtime_err_s("register_s3() bucket must be a string")),
5883                    };
5884                    let region = match &args[1] {
5885                        Value::String(s) => s.clone(),
5886                        _ => return Err(runtime_err_s("register_s3() region must be a string")),
5887                    };
5888                    let access_key = args.get(2).and_then(|v| {
5889                        if let Value::String(s) = v {
5890                            Some(s.clone())
5891                        } else {
5892                            None
5893                        }
5894                    });
5895                    let secret_key = args.get(3).and_then(|v| {
5896                        if let Value::String(s) = v {
5897                            Some(s.clone())
5898                        } else {
5899                            None
5900                        }
5901                    });
5902                    let endpoint = args.get(4).and_then(|v| {
5903                        if let Value::String(s) = v {
5904                            Some(s.clone())
5905                        } else {
5906                            None
5907                        }
5908                    });
5909                    self.engine()
5910                        .register_s3(
5911                            &bucket,
5912                            &region,
5913                            access_key.as_deref(),
5914                            secret_key.as_deref(),
5915                            endpoint.as_deref(),
5916                        )
5917                        .map_err(runtime_err)?;
5918                    Ok(Value::None)
5919                }
5920                #[cfg(not(feature = "s3"))]
5921                Err(runtime_err_s("register_s3() requires the 's3' feature"))
5922            }
5923
5924            // Phase 20: Python FFI
5925            "py_import" => {
5926                #[cfg(feature = "python")]
5927                {
5928                    self.interp_py_import(args)
5929                }
5930                #[cfg(not(feature = "python"))]
5931                Err(runtime_err_s("py_import() requires the 'python' feature"))
5932            }
5933            "py_call" => {
5934                #[cfg(feature = "python")]
5935                {
5936                    self.interp_py_call(args)
5937                }
5938                #[cfg(not(feature = "python"))]
5939                Err(runtime_err_s("py_call() requires the 'python' feature"))
5940            }
5941            "py_eval" => {
5942                #[cfg(feature = "python")]
5943                {
5944                    self.interp_py_eval(args)
5945                }
5946                #[cfg(not(feature = "python"))]
5947                Err(runtime_err_s("py_eval() requires the 'python' feature"))
5948            }
5949            "py_getattr" => {
5950                #[cfg(feature = "python")]
5951                {
5952                    self.interp_py_getattr(args)
5953                }
5954                #[cfg(not(feature = "python"))]
5955                Err(runtime_err_s("py_getattr() requires the 'python' feature"))
5956            }
5957            "py_setattr" => {
5958                #[cfg(feature = "python")]
5959                {
5960                    self.interp_py_setattr(args)
5961                }
5962                #[cfg(not(feature = "python"))]
5963                Err(runtime_err_s("py_setattr() requires the 'python' feature"))
5964            }
5965            "py_to_tl" => {
5966                #[cfg(feature = "python")]
5967                {
5968                    self.interp_py_to_tl(args)
5969                }
5970                #[cfg(not(feature = "python"))]
5971                Err(runtime_err_s("py_to_tl() requires the 'python' feature"))
5972            }
5973
5974            // Phase 21: Schema Evolution
5975            "schema_register" => {
5976                let name = match args.first() {
5977                    Some(Value::String(s)) => s.clone(),
5978                    _ => return Err(runtime_err_s("schema_register: need name")),
5979                };
5980                let version = match args.get(1) {
5981                    Some(Value::Int(v)) => *v,
5982                    _ => return Err(runtime_err_s("schema_register: need version")),
5983                };
5984                let fields = match args.get(2) {
5985                    Some(Value::Map(pairs)) => {
5986                        let mut arrow_fields = Vec::new();
5987                        for (k, v) in pairs {
5988                            let ftype = match v {
5989                                Value::String(s) => s.clone(),
5990                                _ => "string".to_string(),
5991                            };
5992                            arrow_fields.push(ArrowField::new(
5993                                k,
5994                                tl_compiler::schema::type_name_to_arrow_pub(&ftype),
5995                                true,
5996                            ));
5997                        }
5998                        arrow_fields
5999                    }
6000                    _ => return Err(runtime_err_s("schema_register: third arg must be a map")),
6001                };
6002                let schema = Arc::new(ArrowSchema::new(fields));
6003                self.schema_registry
6004                    .register(
6005                        &name,
6006                        version,
6007                        schema,
6008                        tl_compiler::schema::SchemaMetadata::default(),
6009                    )
6010                    .map_err(runtime_err)?;
6011                Ok(Value::None)
6012            }
6013            "schema_get" => {
6014                let name = match args.first() {
6015                    Some(Value::String(s)) => s.clone(),
6016                    _ => return Err(runtime_err_s("schema_get: need name")),
6017                };
6018                let version = match args.get(1) {
6019                    Some(Value::Int(v)) => *v,
6020                    _ => return Err(runtime_err_s("schema_get: need version")),
6021                };
6022                match self.schema_registry.get(&name, version) {
6023                    Some(vs) => {
6024                        let fields: Vec<Value> = vs
6025                            .schema
6026                            .fields()
6027                            .iter()
6028                            .map(|f: &std::sync::Arc<ArrowField>| {
6029                                Value::String(format!("{}: {}", f.name(), f.data_type()))
6030                            })
6031                            .collect();
6032                        Ok(Value::List(fields))
6033                    }
6034                    None => Ok(Value::None),
6035                }
6036            }
6037            "schema_latest" => {
6038                let name = match args.first() {
6039                    Some(Value::String(s)) => s.clone(),
6040                    _ => return Err(runtime_err_s("schema_latest: need name")),
6041                };
6042                match self.schema_registry.latest(&name) {
6043                    Some(vs) => Ok(Value::Int(vs.version)),
6044                    None => Ok(Value::None),
6045                }
6046            }
6047            "schema_history" => {
6048                let name = match args.first() {
6049                    Some(Value::String(s)) => s.clone(),
6050                    _ => return Err(runtime_err_s("schema_history: need name")),
6051                };
6052                let versions = self.schema_registry.versions(&name);
6053                Ok(Value::List(versions.into_iter().map(Value::Int).collect()))
6054            }
6055            "schema_check" => {
6056                let name = match args.first() {
6057                    Some(Value::String(s)) => s.clone(),
6058                    _ => return Err(runtime_err_s("schema_check: need name")),
6059                };
6060                let v1 = match args.get(1) {
6061                    Some(Value::Int(v)) => *v,
6062                    _ => return Err(runtime_err_s("schema_check: need v1")),
6063                };
6064                let v2 = match args.get(2) {
6065                    Some(Value::Int(v)) => *v,
6066                    _ => return Err(runtime_err_s("schema_check: need v2")),
6067                };
6068                let mode_str = match args.get(3) {
6069                    Some(Value::String(s)) => s.clone(),
6070                    _ => "backward".to_string(),
6071                };
6072                let mode = tl_compiler::schema::CompatibilityMode::from_str(&mode_str);
6073                let issues = self
6074                    .schema_registry
6075                    .check_compatibility(&name, v1, v2, mode);
6076                Ok(Value::List(
6077                    issues
6078                        .into_iter()
6079                        .map(|i: tl_compiler::schema::CompatIssue| Value::String(i.to_string()))
6080                        .collect(),
6081                ))
6082            }
6083            "schema_diff" => {
6084                let name = match args.first() {
6085                    Some(Value::String(s)) => s.clone(),
6086                    _ => return Err(runtime_err_s("schema_diff: need name")),
6087                };
6088                let v1 = match args.get(1) {
6089                    Some(Value::Int(v)) => *v,
6090                    _ => return Err(runtime_err_s("schema_diff: need v1")),
6091                };
6092                let v2 = match args.get(2) {
6093                    Some(Value::Int(v)) => *v,
6094                    _ => return Err(runtime_err_s("schema_diff: need v2")),
6095                };
6096                let diffs = self.schema_registry.diff(&name, v1, v2);
6097                Ok(Value::List(
6098                    diffs
6099                        .into_iter()
6100                        .map(|d: tl_compiler::schema::SchemaDiff| Value::String(d.to_string()))
6101                        .collect(),
6102                ))
6103            }
6104            "schema_versions" => {
6105                let name = match args.first() {
6106                    Some(Value::String(s)) => s.clone(),
6107                    _ => return Err(runtime_err_s("schema_versions: need name")),
6108                };
6109                let versions = self.schema_registry.versions(&name);
6110                Ok(Value::List(versions.into_iter().map(Value::Int).collect()))
6111            }
6112            "schema_fields" => {
6113                let name = match args.first() {
6114                    Some(Value::String(s)) => s.clone(),
6115                    _ => return Err(runtime_err_s("schema_fields: need name")),
6116                };
6117                let version = match args.get(1) {
6118                    Some(Value::Int(v)) => *v,
6119                    _ => return Err(runtime_err_s("schema_fields: need version")),
6120                };
6121                let fields = self.schema_registry.fields(&name, version);
6122                Ok(Value::List(
6123                    fields
6124                        .into_iter()
6125                        .map(|(n, t)| Value::String(format!("{}: {}", n, t)))
6126                        .collect(),
6127                ))
6128            }
6129
6130            // Phase 22: decimal() builtin
6131            "decimal" => {
6132                use std::str::FromStr;
6133                match args.first() {
6134                    Some(Value::String(s)) => {
6135                        let cleaned = s.trim_end_matches('d');
6136                        let d = rust_decimal::Decimal::from_str(cleaned)
6137                            .map_err(|e| runtime_err(format!("Invalid decimal: {e}")))?;
6138                        Ok(Value::Decimal(d))
6139                    }
6140                    Some(Value::Int(n)) => Ok(Value::Decimal(rust_decimal::Decimal::from(*n))),
6141                    Some(Value::Float(n)) => {
6142                        use rust_decimal::prelude::FromPrimitive;
6143                        Ok(Value::Decimal(
6144                            rust_decimal::Decimal::from_f64(*n).unwrap_or_default(),
6145                        ))
6146                    }
6147                    Some(Value::Decimal(d)) => Ok(Value::Decimal(*d)),
6148                    _ => Err(runtime_err_s("decimal() expects a string, int, or float")),
6149                }
6150            }
6151
6152            // Phase 23: Secret vault
6153            "secret_get" => {
6154                let key = match args.first() {
6155                    Some(Value::String(s)) => s.clone(),
6156                    _ => return Err(runtime_err_s("secret_get: need key")),
6157                };
6158                if let Some(val) = self.secret_vault.get(&key) {
6159                    Ok(Value::Secret(val.clone()))
6160                } else {
6161                    // Fallback to env var
6162                    let env_key = format!("TL_SECRET_{}", key.to_uppercase());
6163                    match std::env::var(&env_key) {
6164                        Ok(val) => Ok(Value::Secret(val)),
6165                        Err(_) => Ok(Value::None),
6166                    }
6167                }
6168            }
6169            "secret_set" => {
6170                let key = match args.first() {
6171                    Some(Value::String(s)) => s.clone(),
6172                    _ => return Err(runtime_err_s("secret_set: need key")),
6173                };
6174                let val = match args.get(1) {
6175                    Some(Value::String(s)) => s.clone(),
6176                    Some(Value::Secret(s)) => s.clone(),
6177                    _ => return Err(runtime_err_s("secret_set: need string value")),
6178                };
6179                self.secret_vault.insert(key, val);
6180                Ok(Value::None)
6181            }
6182            "secret_delete" => {
6183                let key = match args.first() {
6184                    Some(Value::String(s)) => s.clone(),
6185                    _ => return Err(runtime_err_s("secret_delete: need key")),
6186                };
6187                self.secret_vault.remove(&key);
6188                Ok(Value::None)
6189            }
6190            "secret_list" => {
6191                let keys: Vec<Value> = self
6192                    .secret_vault
6193                    .keys()
6194                    .map(|k| Value::String(k.clone()))
6195                    .collect();
6196                Ok(Value::List(keys))
6197            }
6198            "check_permission" => {
6199                let perm = match args.first() {
6200                    Some(Value::String(s)) => s.clone(),
6201                    _ => return Err(runtime_err_s("check_permission: need permission string")),
6202                };
6203                let allowed = match &self.security_policy {
6204                    Some(policy) => policy.check(&perm),
6205                    None => true,
6206                };
6207                Ok(Value::Bool(allowed))
6208            }
6209
6210            // Phase 23: Data masking
6211            "mask_email" => {
6212                let email = match args.first() {
6213                    Some(Value::String(s)) => s.clone(),
6214                    Some(Value::Secret(s)) => s.clone(),
6215                    _ => return Err(runtime_err_s("mask_email: need string")),
6216                };
6217                let masked = if let Some(at_pos) = email.find('@') {
6218                    let local = &email[..at_pos];
6219                    let domain = &email[at_pos..];
6220                    if local.len() <= 1 {
6221                        format!("*{domain}")
6222                    } else {
6223                        format!("{}***{domain}", &local[..1])
6224                    }
6225                } else {
6226                    "***".to_string()
6227                };
6228                Ok(Value::String(masked))
6229            }
6230            "mask_phone" => {
6231                let phone = match args.first() {
6232                    Some(Value::String(s)) => s.clone(),
6233                    Some(Value::Secret(s)) => s.clone(),
6234                    _ => return Err(runtime_err_s("mask_phone: need string")),
6235                };
6236                let digits: String = phone.chars().filter(|c| c.is_ascii_digit()).collect();
6237                if digits.len() >= 4 {
6238                    let last4 = &digits[digits.len() - 4..];
6239                    Ok(Value::String(format!("***-***-{last4}")))
6240                } else {
6241                    Ok(Value::String("***".to_string()))
6242                }
6243            }
6244            "mask_cc" => {
6245                let cc = match args.first() {
6246                    Some(Value::String(s)) => s.clone(),
6247                    Some(Value::Secret(s)) => s.clone(),
6248                    _ => return Err(runtime_err_s("mask_cc: need string")),
6249                };
6250                let digits: String = cc.chars().filter(|c| c.is_ascii_digit()).collect();
6251                if digits.len() >= 4 {
6252                    let last4 = &digits[digits.len() - 4..];
6253                    Ok(Value::String(format!("****-****-****-{last4}")))
6254                } else {
6255                    Ok(Value::String("****-****-****-****".to_string()))
6256                }
6257            }
6258            "redact" => {
6259                let val = match args.first() {
6260                    Some(Value::String(s)) => s.clone(),
6261                    Some(Value::Secret(s)) => s.clone(),
6262                    Some(v) => format!("{v}"),
6263                    None => return Err(runtime_err_s("redact: need value")),
6264                };
6265                let policy = match args.get(1) {
6266                    Some(Value::String(s)) => s.as_str(),
6267                    _ => "full",
6268                };
6269                let result = match policy {
6270                    "partial" => {
6271                        if val.len() <= 2 {
6272                            "***".to_string()
6273                        } else {
6274                            format!("{}***{}", &val[..1], &val[val.len() - 1..])
6275                        }
6276                    }
6277                    "hash" => {
6278                        use sha2::Digest;
6279                        let hash = sha2::Sha256::digest(val.as_bytes());
6280                        format!("{:x}", hash)
6281                    }
6282                    _ => "***".to_string(), // "full"
6283                };
6284                Ok(Value::String(result))
6285            }
6286            "hash" => {
6287                let val = match args.first() {
6288                    Some(Value::String(s)) => s.clone(),
6289                    Some(Value::Secret(s)) => s.clone(),
6290                    Some(v) => format!("{v}"),
6291                    None => return Err(runtime_err_s("hash: need value")),
6292                };
6293                let algo = match args.get(1) {
6294                    Some(Value::String(s)) => s.as_str(),
6295                    _ => "sha256",
6296                };
6297                let result = match algo {
6298                    "sha256" => {
6299                        use sha2::Digest;
6300                        let hash = sha2::Sha256::digest(val.as_bytes());
6301                        format!("{:x}", hash)
6302                    }
6303                    "sha512" => {
6304                        use sha2::Digest;
6305                        let hash = sha2::Sha512::digest(val.as_bytes());
6306                        format!("{:x}", hash)
6307                    }
6308                    "md5" => {
6309                        use md5::Digest;
6310                        let hash = md5::Md5::digest(val.as_bytes());
6311                        format!("{:x}", hash)
6312                    }
6313                    _ => {
6314                        return Err(runtime_err(format!(
6315                            "hash: unknown algorithm '{algo}', use sha256/sha512/md5"
6316                        )));
6317                    }
6318                };
6319                Ok(Value::String(result))
6320            }
6321
6322            // ── Phase 25: Async builtins ──
6323            #[cfg(feature = "async-runtime")]
6324            "async_read_file" => {
6325                let path = match args.first() {
6326                    Some(Value::String(s)) => s.clone(),
6327                    _ => return Err(runtime_err_s("async_read_file() expects a string path")),
6328                };
6329                if let Some(policy) = &self.security_policy {
6330                    if !policy.check("file_read") {
6331                        return Err(runtime_err_s(
6332                            "async_read_file: file_read not allowed by security policy",
6333                        ));
6334                    }
6335                }
6336                let rt = self.ensure_runtime();
6337                let (tx, rx) = mpsc::channel();
6338                rt.spawn(async move {
6339                    let result = tokio::fs::read_to_string(&path).await;
6340                    let _ = tx.send(
6341                        result
6342                            .map(|s| Value::String(s))
6343                            .map_err(|e| format!("async_read_file error: {e}")),
6344                    );
6345                });
6346                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6347            }
6348            #[cfg(feature = "async-runtime")]
6349            "async_write_file" => {
6350                let path = match args.first() {
6351                    Some(Value::String(s)) => s.clone(),
6352                    _ => return Err(runtime_err_s("async_write_file() expects a string path")),
6353                };
6354                let content = match args.get(1) {
6355                    Some(Value::String(s)) => s.clone(),
6356                    _ => return Err(runtime_err_s("async_write_file() expects string content")),
6357                };
6358                if let Some(policy) = &self.security_policy {
6359                    if !policy.check("file_write") {
6360                        return Err(runtime_err_s(
6361                            "async_write_file: file_write not allowed by security policy",
6362                        ));
6363                    }
6364                }
6365                let rt = self.ensure_runtime();
6366                let (tx, rx) = mpsc::channel();
6367                rt.spawn(async move {
6368                    let result = tokio::fs::write(&path, content.as_bytes()).await;
6369                    let _ = tx.send(
6370                        result
6371                            .map(|_| Value::None)
6372                            .map_err(|e| format!("async_write_file error: {e}")),
6373                    );
6374                });
6375                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6376            }
6377            #[cfg(feature = "async-runtime")]
6378            "async_http_get" => {
6379                let url = match args.first() {
6380                    Some(Value::String(s)) => s.clone(),
6381                    _ => return Err(runtime_err_s("async_http_get() expects a string URL")),
6382                };
6383                if let Some(policy) = &self.security_policy {
6384                    if !policy.check("network") {
6385                        return Err(runtime_err_s(
6386                            "async_http_get: network not allowed by security policy",
6387                        ));
6388                    }
6389                }
6390                let rt = self.ensure_runtime();
6391                let (tx, rx) = mpsc::channel();
6392                rt.spawn(async move {
6393                    let result: Result<Value, String> = async {
6394                        let body = reqwest::get(&url)
6395                            .await
6396                            .map_err(|e| format!("async_http_get error: {e}"))?
6397                            .text()
6398                            .await
6399                            .map_err(|e| format!("async_http_get response error: {e}"))?;
6400                        Ok(Value::String(body))
6401                    }
6402                    .await;
6403                    let _ = tx.send(result);
6404                });
6405                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6406            }
6407            #[cfg(feature = "async-runtime")]
6408            "async_http_post" => {
6409                let url = match args.first() {
6410                    Some(Value::String(s)) => s.clone(),
6411                    _ => return Err(runtime_err_s("async_http_post() expects a string URL")),
6412                };
6413                let body = match args.get(1) {
6414                    Some(Value::String(s)) => s.clone(),
6415                    _ => return Err(runtime_err_s("async_http_post() expects string body")),
6416                };
6417                if let Some(policy) = &self.security_policy {
6418                    if !policy.check("network") {
6419                        return Err(runtime_err_s(
6420                            "async_http_post: network not allowed by security policy",
6421                        ));
6422                    }
6423                }
6424                let rt = self.ensure_runtime();
6425                let (tx, rx) = mpsc::channel();
6426                rt.spawn(async move {
6427                    let result: Result<Value, String> = async {
6428                        let resp = reqwest::Client::new()
6429                            .post(&url)
6430                            .body(body)
6431                            .send()
6432                            .await
6433                            .map_err(|e| format!("async_http_post error: {e}"))?
6434                            .text()
6435                            .await
6436                            .map_err(|e| format!("async_http_post response error: {e}"))?;
6437                        Ok(Value::String(resp))
6438                    }
6439                    .await;
6440                    let _ = tx.send(result);
6441                });
6442                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6443            }
6444            #[cfg(feature = "async-runtime")]
6445            "async_sleep" => {
6446                let ms = match args.first() {
6447                    Some(Value::Int(n)) => *n as u64,
6448                    _ => {
6449                        return Err(runtime_err_s(
6450                            "async_sleep() expects an integer (milliseconds)",
6451                        ));
6452                    }
6453                };
6454                let rt = self.ensure_runtime();
6455                let (tx, rx) = mpsc::channel();
6456                rt.spawn(async move {
6457                    tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
6458                    let _ = tx.send(Ok(Value::None));
6459                });
6460                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6461            }
6462            #[cfg(feature = "async-runtime")]
6463            "select" => {
6464                if args.len() < 2 {
6465                    return Err(runtime_err_s("select() expects at least 2 task arguments"));
6466                }
6467                let mut receivers = Vec::new();
6468                for (i, arg) in args.iter().enumerate() {
6469                    match arg {
6470                        Value::Task(task) => {
6471                            let rx = task
6472                                .receiver
6473                                .lock()
6474                                .unwrap_or_else(|e| e.into_inner())
6475                                .take();
6476                            match rx {
6477                                Some(r) => receivers.push(r),
6478                                None => {
6479                                    return Err(runtime_err(format!(
6480                                        "select: task {} already consumed",
6481                                        i
6482                                    )));
6483                                }
6484                            }
6485                        }
6486                        _ => {
6487                            return Err(runtime_err(format!(
6488                                "select: argument {} is not a task",
6489                                i
6490                            )));
6491                        }
6492                    }
6493                }
6494                let (winner_tx, winner_rx) = mpsc::channel::<Result<Value, String>>();
6495                for rx in receivers {
6496                    let tx = winner_tx.clone();
6497                    std::thread::spawn(move || {
6498                        if let Ok(result) = rx.recv() {
6499                            let _ = tx.send(result);
6500                        }
6501                    });
6502                }
6503                drop(winner_tx);
6504                Ok(Value::Task(Arc::new(TlTask::new(winner_rx))))
6505            }
6506            #[cfg(feature = "async-runtime")]
6507            "race_all" => {
6508                let tasks = match args.first() {
6509                    Some(Value::List(list)) => list.clone(),
6510                    _ => return Err(runtime_err_s("race_all() expects a list of tasks")),
6511                };
6512                if tasks.is_empty() {
6513                    return Err(runtime_err_s(
6514                        "race_all() expects a non-empty list of tasks",
6515                    ));
6516                }
6517                let mut receivers = Vec::new();
6518                for (i, task_val) in tasks.iter().enumerate() {
6519                    match task_val {
6520                        Value::Task(task) => {
6521                            let rx = task
6522                                .receiver
6523                                .lock()
6524                                .unwrap_or_else(|e| e.into_inner())
6525                                .take();
6526                            match rx {
6527                                Some(r) => receivers.push(r),
6528                                None => {
6529                                    return Err(runtime_err(format!(
6530                                        "race_all: task {} already consumed",
6531                                        i
6532                                    )));
6533                                }
6534                            }
6535                        }
6536                        _ => {
6537                            return Err(runtime_err(format!(
6538                                "race_all: element {} is not a task",
6539                                i
6540                            )));
6541                        }
6542                    }
6543                }
6544                let (winner_tx, winner_rx) = mpsc::channel::<Result<Value, String>>();
6545                for rx in receivers {
6546                    let tx = winner_tx.clone();
6547                    std::thread::spawn(move || {
6548                        if let Ok(result) = rx.recv() {
6549                            let _ = tx.send(result);
6550                        }
6551                    });
6552                }
6553                drop(winner_tx);
6554                Ok(Value::Task(Arc::new(TlTask::new(winner_rx))))
6555            }
6556            #[cfg(feature = "async-runtime")]
6557            "async_map" => {
6558                let items = match args.first() {
6559                    Some(Value::List(list)) => list.clone(),
6560                    _ => {
6561                        return Err(runtime_err_s(
6562                            "async_map() expects a list as first argument",
6563                        ));
6564                    }
6565                };
6566                let (closure_params, closure_body, closure_env) = match args.get(1) {
6567                    Some(Value::Closure {
6568                        params,
6569                        body,
6570                        captured_env,
6571                    }) => (params.clone(), body.clone(), captured_env.clone()),
6572                    Some(Value::Function { params, body, .. }) => (
6573                        params.clone(),
6574                        ClosureBody::Block {
6575                            stmts: body.clone(),
6576                            expr: None,
6577                        },
6578                        self.env.scopes.clone(),
6579                    ),
6580                    _ => {
6581                        return Err(runtime_err_s(
6582                            "async_map() expects a function as second argument",
6583                        ));
6584                    }
6585                };
6586                let method_table = self.method_table.clone();
6587                let (tx, rx) = mpsc::channel();
6588                std::thread::spawn(move || {
6589                    let mut results = Vec::new();
6590                    let mut handles = Vec::new();
6591                    for item in items {
6592                        let params = closure_params.clone();
6593                        let body = closure_body.clone();
6594                        let env = closure_env.clone();
6595                        let mt = method_table.clone();
6596                        let handle = std::thread::spawn(move || {
6597                            let mut interp = Interpreter::new();
6598                            interp.env.scopes = env;
6599                            interp.method_table = mt;
6600                            interp.env.push_scope();
6601                            if let Some(p) = params.first() {
6602                                interp.env.set(p.name.clone(), item);
6603                            }
6604                            let result = interp.eval_closure_body(&body);
6605                            interp.env.pop_scope();
6606                            result.map_err(|e| format!("{e}"))
6607                        });
6608                        handles.push(handle);
6609                    }
6610                    for handle in handles {
6611                        match handle.join() {
6612                            Ok(Ok(val)) => results.push(val),
6613                            Ok(Err(e)) => {
6614                                let _ = tx.send(Err(format!("async_map error: {e}")));
6615                                return;
6616                            }
6617                            Err(_) => {
6618                                let _ = tx.send(Err("async_map: thread panicked".to_string()));
6619                                return;
6620                            }
6621                        }
6622                    }
6623                    let _ = tx.send(Ok(Value::List(results)));
6624                });
6625                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6626            }
6627            #[cfg(feature = "async-runtime")]
6628            "async_filter" => {
6629                let items = match args.first() {
6630                    Some(Value::List(list)) => list.clone(),
6631                    _ => {
6632                        return Err(runtime_err_s(
6633                            "async_filter() expects a list as first argument",
6634                        ));
6635                    }
6636                };
6637                let (closure_params, closure_body, closure_env) = match args.get(1) {
6638                    Some(Value::Closure {
6639                        params,
6640                        body,
6641                        captured_env,
6642                    }) => (params.clone(), body.clone(), captured_env.clone()),
6643                    Some(Value::Function { params, body, .. }) => (
6644                        params.clone(),
6645                        ClosureBody::Block {
6646                            stmts: body.clone(),
6647                            expr: None,
6648                        },
6649                        self.env.scopes.clone(),
6650                    ),
6651                    _ => {
6652                        return Err(runtime_err_s(
6653                            "async_filter() expects a function as second argument",
6654                        ));
6655                    }
6656                };
6657                let method_table = self.method_table.clone();
6658                let items_clone = items.clone();
6659                let (tx, rx) = mpsc::channel();
6660                std::thread::spawn(move || {
6661                    let mut handles = Vec::new();
6662                    for item in items {
6663                        let params = closure_params.clone();
6664                        let body = closure_body.clone();
6665                        let env = closure_env.clone();
6666                        let mt = method_table.clone();
6667                        let handle = std::thread::spawn(move || {
6668                            let mut interp = Interpreter::new();
6669                            interp.env.scopes = env;
6670                            interp.method_table = mt;
6671                            interp.env.push_scope();
6672                            if let Some(p) = params.first() {
6673                                interp.env.set(p.name.clone(), item);
6674                            }
6675                            let result = interp.eval_closure_body(&body);
6676                            interp.env.pop_scope();
6677                            result.map_err(|e| format!("{e}"))
6678                        });
6679                        handles.push(handle);
6680                    }
6681                    let mut results = Vec::new();
6682                    for (i, handle) in handles.into_iter().enumerate() {
6683                        match handle.join() {
6684                            Ok(Ok(val)) => {
6685                                if matches!(&val, Value::Bool(true)) {
6686                                    results.push(items_clone[i].clone());
6687                                }
6688                            }
6689                            Ok(Err(e)) => {
6690                                let _ = tx.send(Err(format!("async_filter error: {e}")));
6691                                return;
6692                            }
6693                            Err(_) => {
6694                                let _ = tx.send(Err("async_filter: thread panicked".to_string()));
6695                                return;
6696                            }
6697                        }
6698                    }
6699                    let _ = tx.send(Ok(Value::List(results)));
6700                });
6701                Ok(Value::Task(Arc::new(TlTask::new(rx))))
6702            }
6703            #[cfg(not(feature = "async-runtime"))]
6704            "async_read_file" | "async_write_file" | "async_http_get" | "async_http_post"
6705            | "async_sleep" | "select" | "race_all" | "async_map" | "async_filter" => {
6706                Err(runtime_err(format!(
6707                    "{name}: async builtins require the 'async-runtime' feature"
6708                )))
6709            }
6710
6711            // Phase 27: Data Error Hierarchy builtins
6712            "is_error" => {
6713                if args.is_empty() {
6714                    return Err(runtime_err_s("is_error() expects 1 argument"));
6715                }
6716                let is_err = matches!(&args[0], Value::EnumInstance { type_name, .. } if
6717                    type_name == "DataError" ||
6718                    type_name == "NetworkError" ||
6719                    type_name == "ConnectorError"
6720                );
6721                Ok(Value::Bool(is_err))
6722            }
6723            "error_type" => {
6724                if args.is_empty() {
6725                    return Err(runtime_err_s("error_type() expects 1 argument"));
6726                }
6727                match &args[0] {
6728                    Value::EnumInstance { type_name, .. } => Ok(Value::String(type_name.clone())),
6729                    _ => Ok(Value::None),
6730                }
6731            }
6732
6733            "random" => {
6734                let mut rng = rand::thread_rng();
6735                let val: f64 = rand::Rng::r#gen(&mut rng);
6736                Ok(Value::Float(val))
6737            }
6738            "random_int" => {
6739                if args.len() < 2 {
6740                    return Err(runtime_err("random_int() expects min and max".to_string()));
6741                }
6742                let a = match &args[0] {
6743                    Value::Int(n) => *n,
6744                    _ => return Err(runtime_err("random_int() expects integers".to_string())),
6745                };
6746                let b = match &args[1] {
6747                    Value::Int(n) => *n,
6748                    _ => return Err(runtime_err("random_int() expects integers".to_string())),
6749                };
6750                if a >= b {
6751                    return Err(runtime_err("random_int() requires min < max".to_string()));
6752                }
6753                let mut rng = rand::thread_rng();
6754                let val: i64 = rand::Rng::gen_range(&mut rng, a..b);
6755                Ok(Value::Int(val))
6756            }
6757            "sample" => {
6758                use rand::seq::SliceRandom;
6759                if args.is_empty() {
6760                    return Err(runtime_err("sample() expects a list and count".to_string()));
6761                }
6762                let items = match &args[0] {
6763                    Value::List(items) => items,
6764                    _ => return Err(runtime_err("sample() expects a list".to_string())),
6765                };
6766                let k = match args.get(1) {
6767                    Some(Value::Int(n)) => *n as usize,
6768                    _ => 1,
6769                };
6770                if k > items.len() {
6771                    return Err(runtime_err(
6772                        "sample() count exceeds list length".to_string(),
6773                    ));
6774                }
6775                let mut rng = rand::thread_rng();
6776                let mut indices: Vec<usize> = (0..items.len()).collect();
6777                indices.partial_shuffle(&mut rng, k);
6778                let result: Vec<Value> = indices[..k].iter().map(|&i| items[i].clone()).collect();
6779                if k == 1 && args.get(1).is_none() {
6780                    Ok(result.into_iter().next().unwrap_or(Value::None))
6781                } else {
6782                    Ok(Value::List(result))
6783                }
6784            }
6785            "exp" => {
6786                let x = match args.first() {
6787                    Some(Value::Float(f)) => *f,
6788                    Some(Value::Int(n)) => *n as f64,
6789                    _ => return Err(runtime_err("exp() expects a number".to_string())),
6790                };
6791                Ok(Value::Float(x.exp()))
6792            }
6793            "is_nan" => {
6794                let result = match args.first() {
6795                    Some(Value::Float(f)) => f.is_nan(),
6796                    _ => false,
6797                };
6798                Ok(Value::Bool(result))
6799            }
6800            "is_infinite" => {
6801                let result = match args.first() {
6802                    Some(Value::Float(f)) => f.is_infinite(),
6803                    _ => false,
6804                };
6805                Ok(Value::Bool(result))
6806            }
6807            "sign" => match args.first() {
6808                Some(Value::Int(n)) => {
6809                    if *n > 0 {
6810                        Ok(Value::Int(1))
6811                    } else if *n < 0 {
6812                        Ok(Value::Int(-1))
6813                    } else {
6814                        Ok(Value::Int(0))
6815                    }
6816                }
6817                Some(Value::Float(f)) => {
6818                    if f.is_nan() {
6819                        Ok(Value::Float(f64::NAN))
6820                    } else if *f > 0.0 {
6821                        Ok(Value::Int(1))
6822                    } else if *f < 0.0 {
6823                        Ok(Value::Int(-1))
6824                    } else {
6825                        Ok(Value::Int(0))
6826                    }
6827                }
6828                _ => Err(runtime_err("sign() expects a number".to_string())),
6829            },
6830            "today" => {
6831                use chrono::{Datelike, TimeZone};
6832                let now = chrono::Utc::now();
6833                let midnight = chrono::Utc
6834                    .with_ymd_and_hms(now.year(), now.month(), now.day(), 0, 0, 0)
6835                    .single()
6836                    .ok_or_else(|| runtime_err("Failed to compute today".to_string()))?;
6837                Ok(Value::Int(midnight.timestamp_millis()))
6838            }
6839            "date_add" => {
6840                if args.len() < 3 {
6841                    return Err(runtime_err(
6842                        "date_add() expects datetime, amount, unit".to_string(),
6843                    ));
6844                }
6845                let ms = match &args[0] {
6846                    Value::Int(n) => *n,
6847                    _ => return Err(runtime_err("date_add() first arg must be int".to_string())),
6848                };
6849                let amount = match &args[1] {
6850                    Value::Int(n) => *n,
6851                    _ => return Err(runtime_err("date_add() amount must be int".to_string())),
6852                };
6853                let unit = match &args[2] {
6854                    Value::String(s) => s.as_str(),
6855                    _ => return Err(runtime_err("date_add() unit must be string".to_string())),
6856                };
6857                let offset_ms = match unit {
6858                    "second" | "seconds" => amount * 1000,
6859                    "minute" | "minutes" => amount * 60 * 1000,
6860                    "hour" | "hours" => amount * 3600 * 1000,
6861                    "day" | "days" => amount * 86400 * 1000,
6862                    "week" | "weeks" => amount * 7 * 86400 * 1000,
6863                    _ => return Err(runtime_err(format!("Unknown time unit: {unit}"))),
6864                };
6865                Ok(Value::Int(ms + offset_ms))
6866            }
6867            "date_diff" => {
6868                if args.len() < 3 {
6869                    return Err(runtime_err(
6870                        "date_diff() expects datetime1, datetime2, unit".to_string(),
6871                    ));
6872                }
6873                let ms1 = match &args[0] {
6874                    Value::Int(n) => *n,
6875                    _ => return Err(runtime_err("date_diff() args must be ints".to_string())),
6876                };
6877                let ms2 = match &args[1] {
6878                    Value::Int(n) => *n,
6879                    _ => return Err(runtime_err("date_diff() args must be ints".to_string())),
6880                };
6881                let unit = match &args[2] {
6882                    Value::String(s) => s.as_str(),
6883                    _ => return Err(runtime_err("date_diff() unit must be string".to_string())),
6884                };
6885                let diff_ms = ms1 - ms2;
6886                let result = match unit {
6887                    "second" | "seconds" => diff_ms / 1000,
6888                    "minute" | "minutes" => diff_ms / (60 * 1000),
6889                    "hour" | "hours" => diff_ms / (3600 * 1000),
6890                    "day" | "days" => diff_ms / (86400 * 1000),
6891                    "week" | "weeks" => diff_ms / (7 * 86400 * 1000),
6892                    _ => return Err(runtime_err(format!("Unknown time unit: {unit}"))),
6893                };
6894                Ok(Value::Int(result))
6895            }
6896            "date_trunc" => {
6897                if args.len() < 2 {
6898                    return Err(runtime_err(
6899                        "date_trunc() expects datetime and unit".to_string(),
6900                    ));
6901                }
6902                let ms = match &args[0] {
6903                    Value::Int(n) => *n,
6904                    _ => {
6905                        return Err(runtime_err(
6906                            "date_trunc() first arg must be int".to_string(),
6907                        ));
6908                    }
6909                };
6910                let unit = match &args[1] {
6911                    Value::String(s) => s.as_str(),
6912                    _ => return Err(runtime_err("date_trunc() unit must be string".to_string())),
6913                };
6914                use chrono::{Datelike, TimeZone, Timelike};
6915                let secs = ms / 1000;
6916                let dt = chrono::Utc
6917                    .timestamp_opt(secs, 0)
6918                    .single()
6919                    .ok_or_else(|| runtime_err("Invalid timestamp".to_string()))?;
6920                let truncated = match unit {
6921                    "second" => chrono::Utc
6922                        .with_ymd_and_hms(
6923                            dt.year(),
6924                            dt.month(),
6925                            dt.day(),
6926                            dt.hour(),
6927                            dt.minute(),
6928                            dt.second(),
6929                        )
6930                        .single(),
6931                    "minute" => chrono::Utc
6932                        .with_ymd_and_hms(
6933                            dt.year(),
6934                            dt.month(),
6935                            dt.day(),
6936                            dt.hour(),
6937                            dt.minute(),
6938                            0,
6939                        )
6940                        .single(),
6941                    "hour" => chrono::Utc
6942                        .with_ymd_and_hms(dt.year(), dt.month(), dt.day(), dt.hour(), 0, 0)
6943                        .single(),
6944                    "day" => chrono::Utc
6945                        .with_ymd_and_hms(dt.year(), dt.month(), dt.day(), 0, 0, 0)
6946                        .single(),
6947                    "month" => chrono::Utc
6948                        .with_ymd_and_hms(dt.year(), dt.month(), 1, 0, 0, 0)
6949                        .single(),
6950                    "year" => chrono::Utc
6951                        .with_ymd_and_hms(dt.year(), 1, 1, 0, 0, 0)
6952                        .single(),
6953                    _ => return Err(runtime_err(format!("Unknown truncation unit: {unit}"))),
6954                };
6955                Ok(Value::Int(
6956                    truncated
6957                        .ok_or_else(|| runtime_err("Invalid truncation".to_string()))?
6958                        .timestamp_millis(),
6959                ))
6960            }
6961            "date_extract" => {
6962                if args.len() < 2 {
6963                    return Err(runtime_err(
6964                        "extract() expects datetime and part".to_string(),
6965                    ));
6966                }
6967                let ms = match &args[0] {
6968                    Value::Int(n) => *n,
6969                    _ => return Err(runtime_err("extract() first arg must be int".to_string())),
6970                };
6971                let part = match &args[1] {
6972                    Value::String(s) => s.as_str(),
6973                    _ => return Err(runtime_err("extract() part must be string".to_string())),
6974                };
6975                use chrono::{Datelike, TimeZone, Timelike};
6976                let secs = ms / 1000;
6977                let dt = chrono::Utc
6978                    .timestamp_opt(secs, 0)
6979                    .single()
6980                    .ok_or_else(|| runtime_err("Invalid timestamp".to_string()))?;
6981                let val = match part {
6982                    "year" => dt.year() as i64,
6983                    "month" => dt.month() as i64,
6984                    "day" => dt.day() as i64,
6985                    "hour" => dt.hour() as i64,
6986                    "minute" => dt.minute() as i64,
6987                    "second" => dt.second() as i64,
6988                    "weekday" | "dow" => dt.weekday().num_days_from_monday() as i64,
6989                    "day_of_year" | "doy" => dt.ordinal() as i64,
6990                    _ => return Err(runtime_err(format!("Unknown date part: {part}"))),
6991                };
6992                Ok(Value::Int(val))
6993            }
6994            // ── MCP builtins ──
6995            #[cfg(feature = "mcp")]
6996            "mcp_connect" => {
6997                if args.is_empty() {
6998                    return Err(runtime_err_s(
6999                        "mcp_connect expects at least 1 argument: command or URL",
7000                    ));
7001                }
7002                let command = match &args[0] {
7003                    Value::String(s) => s.clone(),
7004                    _ => {
7005                        return Err(runtime_err_s(
7006                            "mcp_connect: first argument must be a string",
7007                        ));
7008                    }
7009                };
7010
7011                // Build sampling callback using tl-ai
7012                let sampling_cb: Option<tl_mcp::SamplingCallback> =
7013                    Some(Arc::new(|req: tl_mcp::SamplingRequest| {
7014                        let model = req
7015                            .model_hint
7016                            .as_deref()
7017                            .unwrap_or("claude-sonnet-4-20250514");
7018                        let messages: Vec<serde_json::Value> = req
7019                            .messages
7020                            .iter()
7021                            .map(|(role, content)| {
7022                                serde_json::json!({"role": role, "content": content})
7023                            })
7024                            .collect();
7025                        let response = tl_ai::chat_with_tools(
7026                            model,
7027                            req.system_prompt.as_deref(),
7028                            &messages,
7029                            &[],  // no tools for sampling
7030                            None, // base_url
7031                            None, // api_key
7032                            None, // output_format
7033                        )
7034                        .map_err(|e| format!("Sampling LLM error: {e}"))?;
7035                        match response {
7036                            tl_ai::LlmResponse::Text(text) => Ok(tl_mcp::SamplingResponse {
7037                                model: model.to_string(),
7038                                content: text,
7039                                stop_reason: Some("endTurn".to_string()),
7040                            }),
7041                            tl_ai::LlmResponse::ToolUse(_) => {
7042                                Err("Sampling does not support tool use".to_string())
7043                            }
7044                        }
7045                    }));
7046
7047                // Auto-detect HTTP URL vs subprocess command
7048                let client = if command.starts_with("http://") || command.starts_with("https://") {
7049                    tl_mcp::McpClient::connect_http_with_sampling(&command, sampling_cb)
7050                        .map_err(|e| runtime_err(format!("mcp_connect (HTTP) failed: {e}")))?
7051                } else {
7052                    let cmd_args: Vec<String> = args[1..]
7053                        .iter()
7054                        .map(|a| match a {
7055                            Value::String(s) => s.clone(),
7056                            other => format!("{}", other),
7057                        })
7058                        .collect();
7059                    tl_mcp::McpClient::connect_with_sampling(
7060                        &command,
7061                        &cmd_args,
7062                        self.security_policy.as_ref(),
7063                        sampling_cb,
7064                    )
7065                    .map_err(|e| runtime_err(format!("mcp_connect failed: {e}")))?
7066                };
7067                Ok(Value::McpClient(Arc::new(client)))
7068            }
7069            #[cfg(feature = "mcp")]
7070            "mcp_list_tools" => {
7071                if args.is_empty() {
7072                    return Err(runtime_err_s("mcp_list_tools expects 1 argument: client"));
7073                }
7074                match &args[0] {
7075                    Value::McpClient(client) => {
7076                        let tools = client
7077                            .list_tools()
7078                            .map_err(|e| runtime_err(format!("mcp_list_tools failed: {e}")))?;
7079                        let tool_values: Vec<Value> = tools
7080                            .iter()
7081                            .map(|tool| {
7082                                let mut pairs: Vec<(String, Value)> = Vec::new();
7083                                pairs.push((
7084                                    "name".to_string(),
7085                                    Value::String(tool.name.to_string()),
7086                                ));
7087                                if let Some(desc) = &tool.description {
7088                                    pairs.push((
7089                                        "description".to_string(),
7090                                        Value::String(desc.to_string()),
7091                                    ));
7092                                }
7093                                let schema_json =
7094                                    serde_json::to_string(&*tool.input_schema).unwrap_or_default();
7095                                pairs
7096                                    .push(("input_schema".to_string(), Value::String(schema_json)));
7097                                Value::Map(pairs)
7098                            })
7099                            .collect();
7100                        Ok(Value::List(tool_values))
7101                    }
7102                    _ => Err(runtime_err_s(
7103                        "mcp_list_tools: argument must be an mcp_client",
7104                    )),
7105                }
7106            }
7107            #[cfg(feature = "mcp")]
7108            "mcp_call_tool" => {
7109                if args.len() < 2 {
7110                    return Err(runtime_err_s(
7111                        "mcp_call_tool expects 2-3 arguments: client, tool_name, [args]",
7112                    ));
7113                }
7114                let client = match &args[0] {
7115                    Value::McpClient(c) => c.clone(),
7116                    _ => {
7117                        return Err(runtime_err_s(
7118                            "mcp_call_tool: first argument must be an mcp_client",
7119                        ));
7120                    }
7121                };
7122                let tool_name = match &args[1] {
7123                    Value::String(s) => s.clone(),
7124                    _ => return Err(runtime_err_s("mcp_call_tool: tool_name must be a string")),
7125                };
7126                let arguments = if args.len() > 2 {
7127                    value_to_json(&args[2])
7128                } else {
7129                    serde_json::Value::Object(serde_json::Map::new())
7130                };
7131                let result = client
7132                    .call_tool(&tool_name, arguments)
7133                    .map_err(|e| runtime_err(format!("mcp_call_tool failed: {e}")))?;
7134                let mut content_parts: Vec<Value> = Vec::new();
7135                for content in &result.content {
7136                    if let Some(text_content) = content.raw.as_text() {
7137                        content_parts.push(Value::String(text_content.text.clone()));
7138                    }
7139                }
7140                let mut pairs: Vec<(String, Value)> = Vec::new();
7141                if content_parts.len() == 1 {
7142                    pairs.push((
7143                        "content".to_string(),
7144                        content_parts.into_iter().next().unwrap(),
7145                    ));
7146                } else {
7147                    pairs.push(("content".to_string(), Value::List(content_parts)));
7148                }
7149                pairs.push((
7150                    "is_error".to_string(),
7151                    Value::Bool(result.is_error.unwrap_or(false)),
7152                ));
7153                Ok(Value::Map(pairs))
7154            }
7155            #[cfg(feature = "mcp")]
7156            "mcp_disconnect" => {
7157                if args.is_empty() {
7158                    return Err(runtime_err_s("mcp_disconnect expects 1 argument: client"));
7159                }
7160                match &args[0] {
7161                    Value::McpClient(_) => Ok(Value::None),
7162                    _ => Err(runtime_err_s(
7163                        "mcp_disconnect: argument must be an mcp_client",
7164                    )),
7165                }
7166            }
7167            #[cfg(feature = "mcp")]
7168            "mcp_ping" => {
7169                if args.is_empty() {
7170                    return Err(runtime_err_s("mcp_ping expects 1 argument: client"));
7171                }
7172                match &args[0] {
7173                    Value::McpClient(client) => {
7174                        client
7175                            .ping()
7176                            .map_err(|e| runtime_err(format!("mcp_ping failed: {e}")))?;
7177                        Ok(Value::Bool(true))
7178                    }
7179                    _ => Err(runtime_err_s("mcp_ping: argument must be an mcp_client")),
7180                }
7181            }
7182            #[cfg(feature = "mcp")]
7183            "mcp_server_info" => {
7184                if args.is_empty() {
7185                    return Err(runtime_err_s("mcp_server_info expects 1 argument: client"));
7186                }
7187                match &args[0] {
7188                    Value::McpClient(client) => match client.server_info() {
7189                        Some(info) => {
7190                            let pairs: Vec<(String, Value)> = vec![
7191                                (
7192                                    "name".to_string(),
7193                                    Value::String(info.server_info.name.clone()),
7194                                ),
7195                                (
7196                                    "version".to_string(),
7197                                    Value::String(info.server_info.version.clone()),
7198                                ),
7199                            ];
7200                            Ok(Value::Map(pairs))
7201                        }
7202                        None => Ok(Value::None),
7203                    },
7204                    _ => Err(runtime_err_s(
7205                        "mcp_server_info: argument must be an mcp_client",
7206                    )),
7207                }
7208            }
7209            #[cfg(feature = "mcp")]
7210            "mcp_serve" => {
7211                // Check network permission
7212                if let Some(policy) = &self.security_policy
7213                    && !policy.allow_network
7214                {
7215                    return Err(runtime_err_s(
7216                        "mcp_serve: network access not allowed by security policy",
7217                    ));
7218                }
7219
7220                if args.is_empty() {
7221                    return Err(runtime_err_s(
7222                        "mcp_serve expects 1 argument: list of tool definitions",
7223                    ));
7224                }
7225                let tool_list = match &args[0] {
7226                    Value::List(items) => items.clone(),
7227                    _ => {
7228                        return Err(runtime_err_s(
7229                            "mcp_serve: argument must be a list of tool maps",
7230                        ));
7231                    }
7232                };
7233
7234                // Extract tool definitions and function values
7235                let mut channel_tools = Vec::new();
7236                let mut tool_handlers: std::collections::HashMap<String, Value> =
7237                    std::collections::HashMap::new();
7238
7239                for item in &tool_list {
7240                    let pairs = match item {
7241                        Value::Map(p) => p,
7242                        _ => {
7243                            return Err(runtime_err_s(
7244                                "mcp_serve: each tool must be a map with name, description, handler",
7245                            ));
7246                        }
7247                    };
7248                    let mut tname = String::new();
7249                    let mut description = String::new();
7250                    let mut handler = None;
7251                    let mut input_schema = serde_json::json!({"type": "object"});
7252
7253                    for (k, v) in pairs {
7254                        match k.as_str() {
7255                            "name" => {
7256                                if let Value::String(s) = v {
7257                                    tname = s.clone();
7258                                }
7259                            }
7260                            "description" => {
7261                                if let Value::String(s) = v {
7262                                    description = s.clone();
7263                                }
7264                            }
7265                            "handler" => {
7266                                handler = Some(v.clone());
7267                            }
7268                            "input_schema" | "parameters" => {
7269                                if let Value::String(s) = v
7270                                    && let Ok(parsed) = serde_json::from_str::<serde_json::Value>(s)
7271                                {
7272                                    input_schema = parsed;
7273                                }
7274                            }
7275                            _ => {}
7276                        }
7277                    }
7278
7279                    if tname.is_empty() {
7280                        return Err(runtime_err_s("mcp_serve: tool missing 'name'"));
7281                    }
7282                    if let Some(h) = handler {
7283                        tool_handlers.insert(tname.clone(), h);
7284                    }
7285
7286                    channel_tools.push(tl_mcp::server::ChannelToolDef {
7287                        name: tname,
7288                        description,
7289                        input_schema,
7290                    });
7291                }
7292
7293                // Build server with channel-based tools
7294                let (builder, rx) = tl_mcp::server::TlServerHandler::builder()
7295                    .name("tl-mcp-server")
7296                    .version("1.0.0")
7297                    .channel_tools(channel_tools);
7298                let server_handler = builder.build();
7299
7300                // Start server on background thread
7301                let _server_handle = tl_mcp::server::serve_stdio_background(server_handler);
7302
7303                // Main dispatch loop: process tool call requests from the MCP server
7304                while let Ok(req) = rx.recv() {
7305                    let result = if let Some(func) = tool_handlers.get(&req.tool_name) {
7306                        // Convert JSON args to interpreter Values
7307                        let call_args = self.agent_json_to_values(&req.arguments);
7308                        match self.call_function_value(func, &call_args) {
7309                            Ok(val) => Ok(serde_json::json!(format!("{val}"))),
7310                            Err(e) => Err(format!("{e}")),
7311                        }
7312                    } else {
7313                        Err(format!("Unknown tool: {}", req.tool_name))
7314                    };
7315                    let _ = req.response_tx.send(result);
7316                }
7317
7318                Ok(Value::None)
7319            }
7320
7321            // ── MCP Resources & Prompts ──
7322            #[cfg(feature = "mcp")]
7323            "mcp_list_resources" => {
7324                if args.is_empty() {
7325                    return Err(runtime_err_s(
7326                        "mcp_list_resources expects 1 argument: client",
7327                    ));
7328                }
7329                match &args[0] {
7330                    Value::McpClient(client) => {
7331                        let resources = client
7332                            .list_resources()
7333                            .map_err(|e| runtime_err(format!("mcp_list_resources failed: {e}")))?;
7334                        let vals: Vec<Value> = resources
7335                            .iter()
7336                            .map(|r| {
7337                                let mut pairs: Vec<(String, Value)> = Vec::new();
7338                                pairs.push(("uri".to_string(), Value::String(r.uri.clone())));
7339                                pairs.push(("name".to_string(), Value::String(r.name.clone())));
7340                                if let Some(desc) = &r.description {
7341                                    pairs.push((
7342                                        "description".to_string(),
7343                                        Value::String(desc.clone()),
7344                                    ));
7345                                }
7346                                if let Some(mime) = &r.mime_type {
7347                                    pairs.push((
7348                                        "mime_type".to_string(),
7349                                        Value::String(mime.clone()),
7350                                    ));
7351                                }
7352                                Value::Map(pairs)
7353                            })
7354                            .collect();
7355                        Ok(Value::List(vals))
7356                    }
7357                    _ => Err(runtime_err_s(
7358                        "mcp_list_resources: argument must be an mcp_client",
7359                    )),
7360                }
7361            }
7362
7363            #[cfg(feature = "mcp")]
7364            "mcp_read_resource" => {
7365                if args.len() < 2 {
7366                    return Err(runtime_err_s(
7367                        "mcp_read_resource expects 2 arguments: client, uri",
7368                    ));
7369                }
7370                let client = match &args[0] {
7371                    Value::McpClient(c) => c.clone(),
7372                    _ => {
7373                        return Err(runtime_err_s(
7374                            "mcp_read_resource: first argument must be an mcp_client",
7375                        ));
7376                    }
7377                };
7378                let uri = match &args[1] {
7379                    Value::String(s) => s.clone(),
7380                    _ => return Err(runtime_err_s("mcp_read_resource: uri must be a string")),
7381                };
7382                let result = client
7383                    .read_resource(&uri)
7384                    .map_err(|e| runtime_err(format!("mcp_read_resource failed: {e}")))?;
7385                // Serialize ResourceContents via JSON to avoid direct rmcp type dependency
7386                let contents: Vec<Value> = result
7387                    .contents
7388                    .iter()
7389                    .map(|content| {
7390                        let mut pairs: Vec<(String, Value)> = Vec::new();
7391                        let json = serde_json::to_value(content).unwrap_or_default();
7392                        if let Some(uri_s) = json.get("uri").and_then(|v| v.as_str()) {
7393                            pairs.push(("uri".to_string(), Value::String(uri_s.to_string())));
7394                        }
7395                        if let Some(mime) = json.get("mimeType").and_then(|v| v.as_str()) {
7396                            pairs.push(("mime_type".to_string(), Value::String(mime.to_string())));
7397                        }
7398                        if let Some(text) = json.get("text").and_then(|v| v.as_str()) {
7399                            pairs.push(("text".to_string(), Value::String(text.to_string())));
7400                        }
7401                        if let Some(blob) = json.get("blob").and_then(|v| v.as_str()) {
7402                            pairs.push(("blob".to_string(), Value::String(blob.to_string())));
7403                        }
7404                        Value::Map(pairs)
7405                    })
7406                    .collect();
7407                if contents.len() == 1 {
7408                    Ok(contents.into_iter().next().unwrap())
7409                } else {
7410                    Ok(Value::List(contents))
7411                }
7412            }
7413
7414            #[cfg(feature = "mcp")]
7415            "mcp_list_prompts" => {
7416                if args.is_empty() {
7417                    return Err(runtime_err_s("mcp_list_prompts expects 1 argument: client"));
7418                }
7419                match &args[0] {
7420                    Value::McpClient(client) => {
7421                        let prompts = client
7422                            .list_prompts()
7423                            .map_err(|e| runtime_err(format!("mcp_list_prompts failed: {e}")))?;
7424                        let vals: Vec<Value> = prompts
7425                            .iter()
7426                            .map(|p| {
7427                                let mut pairs: Vec<(String, Value)> = Vec::new();
7428                                pairs.push(("name".to_string(), Value::String(p.name.clone())));
7429                                if let Some(desc) = &p.description {
7430                                    pairs.push((
7431                                        "description".to_string(),
7432                                        Value::String(desc.clone()),
7433                                    ));
7434                                }
7435                                if let Some(prompt_args) = &p.arguments {
7436                                    let arg_vals: Vec<Value> = prompt_args
7437                                        .iter()
7438                                        .map(|a| {
7439                                            let mut arg_pairs: Vec<(String, Value)> = Vec::new();
7440                                            arg_pairs.push((
7441                                                "name".to_string(),
7442                                                Value::String(a.name.clone()),
7443                                            ));
7444                                            if let Some(desc) = &a.description {
7445                                                arg_pairs.push((
7446                                                    "description".to_string(),
7447                                                    Value::String(desc.clone()),
7448                                                ));
7449                                            }
7450                                            arg_pairs.push((
7451                                                "required".to_string(),
7452                                                Value::Bool(a.required.unwrap_or(false)),
7453                                            ));
7454                                            Value::Map(arg_pairs)
7455                                        })
7456                                        .collect();
7457                                    pairs.push(("arguments".to_string(), Value::List(arg_vals)));
7458                                }
7459                                Value::Map(pairs)
7460                            })
7461                            .collect();
7462                        Ok(Value::List(vals))
7463                    }
7464                    _ => Err(runtime_err_s(
7465                        "mcp_list_prompts: argument must be an mcp_client",
7466                    )),
7467                }
7468            }
7469
7470            #[cfg(feature = "mcp")]
7471            "mcp_get_prompt" => {
7472                if args.len() < 2 {
7473                    return Err(runtime_err_s(
7474                        "mcp_get_prompt expects 2-3 arguments: client, name, [args]",
7475                    ));
7476                }
7477                let client = match &args[0] {
7478                    Value::McpClient(c) => c.clone(),
7479                    _ => {
7480                        return Err(runtime_err_s(
7481                            "mcp_get_prompt: first argument must be an mcp_client",
7482                        ));
7483                    }
7484                };
7485                let name_arg = match &args[1] {
7486                    Value::String(s) => s.clone(),
7487                    _ => return Err(runtime_err_s("mcp_get_prompt: name must be a string")),
7488                };
7489                let prompt_args = if args.len() > 2 {
7490                    let json = value_to_json(&args[2]);
7491                    json.as_object().cloned()
7492                } else {
7493                    None
7494                };
7495                let result = client
7496                    .get_prompt(&name_arg, prompt_args)
7497                    .map_err(|e| runtime_err(format!("mcp_get_prompt failed: {e}")))?;
7498                let mut pairs: Vec<(String, Value)> = Vec::new();
7499                if let Some(desc) = &result.description {
7500                    pairs.push(("description".to_string(), Value::String(desc.clone())));
7501                }
7502                // Serialize PromptMessage via JSON to avoid direct rmcp type dependency
7503                let messages: Vec<Value> = result
7504                    .messages
7505                    .iter()
7506                    .map(|m| {
7507                        let mut msg_pairs: Vec<(String, Value)> = Vec::new();
7508                        let msg_json = serde_json::to_value(m).unwrap_or_default();
7509                        // role is serialized as "user" or "assistant"
7510                        if let Some(role) = msg_json.get("role").and_then(|v| v.as_str()) {
7511                            msg_pairs.push(("role".to_string(), Value::String(role.to_string())));
7512                        }
7513                        // content is an object with "type" field; extract text if it's a text message
7514                        if let Some(content) = msg_json.get("content") {
7515                            if let Some(text) = content.get("text").and_then(|v| v.as_str()) {
7516                                msg_pairs
7517                                    .push(("content".to_string(), Value::String(text.to_string())));
7518                            } else {
7519                                msg_pairs.push((
7520                                    "content".to_string(),
7521                                    Value::String(content.to_string()),
7522                                ));
7523                            }
7524                        }
7525                        Value::Map(msg_pairs)
7526                    })
7527                    .collect();
7528                pairs.push(("messages".to_string(), Value::List(messages)));
7529                Ok(Value::Map(pairs))
7530            }
7531
7532            _ => Err(runtime_err(format!("Unknown builtin: {name}"))),
7533        }
7534    }
7535
7536    /// Call a method on an object via dot notation
7537    /// Deep-clone a Value, recursively copying containers.
7538    fn deep_clone_interp_value(&self, val: &Value) -> Result<Value, TlError> {
7539        match val {
7540            Value::List(items) => {
7541                let cloned: Result<Vec<_>, _> = items
7542                    .iter()
7543                    .map(|v| self.deep_clone_interp_value(v))
7544                    .collect();
7545                Ok(Value::List(cloned?))
7546            }
7547            Value::Map(pairs) => {
7548                let cloned: Result<Vec<_>, _> = pairs
7549                    .iter()
7550                    .map(|(k, v)| Ok((k.clone(), self.deep_clone_interp_value(v)?)))
7551                    .collect();
7552                Ok(Value::Map(cloned?))
7553            }
7554            Value::Set(items) => {
7555                let cloned: Result<Vec<_>, _> = items
7556                    .iter()
7557                    .map(|v| self.deep_clone_interp_value(v))
7558                    .collect();
7559                Ok(Value::Set(cloned?))
7560            }
7561            Value::StructInstance { type_name, fields } => {
7562                let mut cloned_fields = HashMap::new();
7563                for (k, v) in fields {
7564                    cloned_fields.insert(k.clone(), self.deep_clone_interp_value(v)?);
7565                }
7566                Ok(Value::StructInstance {
7567                    type_name: type_name.clone(),
7568                    fields: cloned_fields,
7569                })
7570            }
7571            Value::EnumInstance {
7572                type_name,
7573                variant,
7574                fields,
7575            } => {
7576                let cloned_fields: Result<Vec<_>, _> = fields
7577                    .iter()
7578                    .map(|v| self.deep_clone_interp_value(v))
7579                    .collect();
7580                Ok(Value::EnumInstance {
7581                    type_name: type_name.clone(),
7582                    variant: variant.clone(),
7583                    fields: cloned_fields?,
7584                })
7585            }
7586            Value::Ref(inner) => self.deep_clone_interp_value(inner),
7587            Value::Moved => Err(runtime_err("Cannot clone a moved value".to_string())),
7588            Value::Task(_) => Err(runtime_err("Cannot clone a task".to_string())),
7589            Value::Channel(_) => Err(runtime_err("Cannot clone a channel".to_string())),
7590            Value::Generator(_) => Err(runtime_err("Cannot clone a generator".to_string())),
7591            other => Ok(other.clone()),
7592        }
7593    }
7594
7595    fn call_method(&mut self, obj: &Value, method: &str, args: &[Value]) -> Result<Value, TlError> {
7596        // Universal .clone() method — deep copy any value
7597        if method == "clone" {
7598            return self.deep_clone_interp_value(obj);
7599        }
7600        // Unwrap Ref for method dispatch — methods can be called through references
7601        let obj = match obj {
7602            Value::Ref(inner) => inner.as_ref(),
7603            other => other,
7604        };
7605        // String methods
7606        if let Value::String(s) = obj {
7607            return match method {
7608                "len" => Ok(Value::Int(s.len() as i64)),
7609                "split" => {
7610                    let sep = match args.first() {
7611                        Some(Value::String(sep)) => sep.as_str().to_string(),
7612                        _ => return Err(runtime_err_s("split() expects a string separator")),
7613                    };
7614                    let parts: Vec<Value> = s
7615                        .split(&sep)
7616                        .map(|p| Value::String(p.to_string()))
7617                        .collect();
7618                    Ok(Value::List(parts))
7619                }
7620                "trim" => Ok(Value::String(s.trim().to_string())),
7621                "contains" => {
7622                    let needle = match args.first() {
7623                        Some(Value::String(n)) => n.as_str(),
7624                        _ => return Err(runtime_err_s("contains() expects a string")),
7625                    };
7626                    Ok(Value::Bool(s.contains(needle)))
7627                }
7628                "replace" => {
7629                    if args.len() < 2 {
7630                        return Err(runtime_err_s("replace() expects 2 arguments"));
7631                    }
7632                    if let (Value::String(from), Value::String(to)) = (&args[0], &args[1]) {
7633                        Ok(Value::String(s.replace(from.as_str(), to.as_str())))
7634                    } else {
7635                        Err(runtime_err_s("replace() expects string arguments"))
7636                    }
7637                }
7638                "starts_with" => {
7639                    let prefix = match args.first() {
7640                        Some(Value::String(p)) => p.as_str(),
7641                        _ => return Err(runtime_err_s("starts_with() expects a string")),
7642                    };
7643                    Ok(Value::Bool(s.starts_with(prefix)))
7644                }
7645                "ends_with" => {
7646                    let suffix = match args.first() {
7647                        Some(Value::String(su)) => su.as_str(),
7648                        _ => return Err(runtime_err_s("ends_with() expects a string")),
7649                    };
7650                    Ok(Value::Bool(s.ends_with(suffix)))
7651                }
7652                "to_upper" => Ok(Value::String(s.to_uppercase())),
7653                "to_lower" => Ok(Value::String(s.to_lowercase())),
7654                "chars" => {
7655                    let chars: Vec<Value> =
7656                        s.chars().map(|c| Value::String(c.to_string())).collect();
7657                    Ok(Value::List(chars))
7658                }
7659                "repeat" => {
7660                    let n = match args.first() {
7661                        Some(Value::Int(n)) => *n as usize,
7662                        _ => return Err(runtime_err_s("repeat() expects an integer")),
7663                    };
7664                    Ok(Value::String(s.repeat(n)))
7665                }
7666                "index_of" => {
7667                    let needle = match args.first() {
7668                        Some(Value::String(n)) => n.as_str(),
7669                        _ => return Err(runtime_err_s("index_of() expects a string")),
7670                    };
7671                    Ok(Value::Int(s.find(needle).map(|i| i as i64).unwrap_or(-1)))
7672                }
7673                "substring" => {
7674                    if args.len() < 2 {
7675                        return Err(runtime_err_s("substring() expects start and end"));
7676                    }
7677                    let start = match &args[0] {
7678                        Value::Int(n) => *n as usize,
7679                        _ => return Err(runtime_err_s("substring() expects integers")),
7680                    };
7681                    let end = match &args[1] {
7682                        Value::Int(n) => *n as usize,
7683                        _ => return Err(runtime_err_s("substring() expects integers")),
7684                    };
7685                    let end = end.min(s.len());
7686                    let start = start.min(end);
7687                    Ok(Value::String(s[start..end].to_string()))
7688                }
7689                "pad_left" => {
7690                    if args.is_empty() {
7691                        return Err(runtime_err_s("pad_left() expects width"));
7692                    }
7693                    let width = match &args[0] {
7694                        Value::Int(n) => *n as usize,
7695                        _ => return Err(runtime_err_s("pad_left() expects integer width")),
7696                    };
7697                    let ch = match args.get(1) {
7698                        Some(Value::String(c)) => c.chars().next().unwrap_or(' '),
7699                        _ => ' ',
7700                    };
7701                    if s.len() >= width {
7702                        Ok(Value::String(s.clone()))
7703                    } else {
7704                        Ok(Value::String(format!(
7705                            "{}{}",
7706                            std::iter::repeat_n(ch, width - s.len()).collect::<String>(),
7707                            s
7708                        )))
7709                    }
7710                }
7711                "pad_right" => {
7712                    if args.is_empty() {
7713                        return Err(runtime_err_s("pad_right() expects width"));
7714                    }
7715                    let width = match &args[0] {
7716                        Value::Int(n) => *n as usize,
7717                        _ => return Err(runtime_err_s("pad_right() expects integer width")),
7718                    };
7719                    let ch = match args.get(1) {
7720                        Some(Value::String(c)) => c.chars().next().unwrap_or(' '),
7721                        _ => ' ',
7722                    };
7723                    if s.len() >= width {
7724                        Ok(Value::String(s.clone()))
7725                    } else {
7726                        Ok(Value::String(format!(
7727                            "{}{}",
7728                            s,
7729                            std::iter::repeat_n(ch, width - s.len()).collect::<String>()
7730                        )))
7731                    }
7732                }
7733                "join" => {
7734                    if let Some(Value::List(items)) = args.first() {
7735                        let parts: Vec<String> = items.iter().map(|v| format!("{v}")).collect();
7736                        Ok(Value::String(parts.join(s.as_str())))
7737                    } else {
7738                        Err(runtime_err_s("join() expects a list argument"))
7739                    }
7740                }
7741                "trim_start" => Ok(Value::String(s.trim_start().to_string())),
7742                "trim_end" => Ok(Value::String(s.trim_end().to_string())),
7743                "count" => {
7744                    let needle = match args.first() {
7745                        Some(Value::String(n)) => n.as_str(),
7746                        _ => return Err(runtime_err_s("count() expects a string")),
7747                    };
7748                    Ok(Value::Int(s.matches(needle).count() as i64))
7749                }
7750                "is_empty" => Ok(Value::Bool(s.is_empty())),
7751                "is_numeric" => Ok(Value::Bool(
7752                    s.chars()
7753                        .all(|c| c.is_ascii_digit() || c == '.' || c == '-'),
7754                )),
7755                "is_alpha" => Ok(Value::Bool(
7756                    !s.is_empty() && s.chars().all(|c| c.is_alphabetic()),
7757                )),
7758                "strip_prefix" => {
7759                    let prefix = match args.first() {
7760                        Some(Value::String(p)) => p.as_str(),
7761                        _ => return Err(runtime_err_s("strip_prefix() expects a string")),
7762                    };
7763                    Ok(match s.strip_prefix(prefix) {
7764                        Some(rest) => Value::String(rest.to_string()),
7765                        None => Value::String(s.clone()),
7766                    })
7767                }
7768                "strip_suffix" => {
7769                    let suffix = match args.first() {
7770                        Some(Value::String(su)) => su.as_str(),
7771                        _ => return Err(runtime_err_s("strip_suffix() expects a string")),
7772                    };
7773                    Ok(match s.strip_suffix(suffix) {
7774                        Some(rest) => Value::String(rest.to_string()),
7775                        None => Value::String(s.clone()),
7776                    })
7777                }
7778                _ => Err(runtime_err(format!("String has no method `{method}`"))),
7779            };
7780        }
7781
7782        // List methods
7783        if let Value::List(items) = obj {
7784            return match method {
7785                "len" => Ok(Value::Int(items.len() as i64)),
7786                "push" => {
7787                    let mut new_items = items.clone();
7788                    for arg in args {
7789                        new_items.push(arg.clone());
7790                    }
7791                    Ok(Value::List(new_items))
7792                }
7793                "map" => {
7794                    if let Some(func) = args.first() {
7795                        let mut result = Vec::new();
7796                        for item in items {
7797                            result.push(self.call_function(func, std::slice::from_ref(item))?);
7798                        }
7799                        Ok(Value::List(result))
7800                    } else {
7801                        Err(runtime_err_s("map() expects a function argument"))
7802                    }
7803                }
7804                "filter" => {
7805                    if let Some(func) = args.first() {
7806                        let mut result = Vec::new();
7807                        for item in items {
7808                            let keep = self.call_function(func, std::slice::from_ref(item))?;
7809                            if keep.is_truthy() {
7810                                result.push(item.clone());
7811                            }
7812                        }
7813                        Ok(Value::List(result))
7814                    } else {
7815                        Err(runtime_err_s("filter() expects a function argument"))
7816                    }
7817                }
7818                "reduce" => {
7819                    if args.len() < 2 {
7820                        return Err(runtime_err_s(
7821                            "reduce() expects a function and initial value",
7822                        ));
7823                    }
7824                    let func = &args[0];
7825                    let mut acc = args[1].clone();
7826                    for item in items {
7827                        acc = self.call_function(func, &[acc, item.clone()])?;
7828                    }
7829                    Ok(acc)
7830                }
7831                "sort" => {
7832                    let mut sorted = items.clone();
7833                    sorted.sort_by(|a, b| match (a, b) {
7834                        (Value::Int(x), Value::Int(y)) => x.cmp(y),
7835                        (Value::Float(x), Value::Float(y)) => {
7836                            x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal)
7837                        }
7838                        (Value::String(x), Value::String(y)) => x.cmp(y),
7839                        _ => std::cmp::Ordering::Equal,
7840                    });
7841                    Ok(Value::List(sorted))
7842                }
7843                "reverse" => {
7844                    let mut reversed = items.clone();
7845                    reversed.reverse();
7846                    Ok(Value::List(reversed))
7847                }
7848                "contains" => {
7849                    if args.is_empty() {
7850                        return Err(runtime_err_s("contains() expects a value"));
7851                    }
7852                    let needle = &args[0];
7853                    let found = items.iter().any(|item| match (item, needle) {
7854                        (Value::Int(a), Value::Int(b)) => a == b,
7855                        (Value::Float(a), Value::Float(b)) => a == b,
7856                        (Value::String(a), Value::String(b)) => a == b,
7857                        (Value::Bool(a), Value::Bool(b)) => a == b,
7858                        (Value::None, Value::None) => true,
7859                        _ => false,
7860                    });
7861                    Ok(Value::Bool(found))
7862                }
7863                "index_of" => {
7864                    if args.is_empty() {
7865                        return Err(runtime_err_s("index_of() expects a value"));
7866                    }
7867                    let needle = &args[0];
7868                    let idx = items.iter().position(|item| match (item, needle) {
7869                        (Value::Int(a), Value::Int(b)) => a == b,
7870                        (Value::Float(a), Value::Float(b)) => a == b,
7871                        (Value::String(a), Value::String(b)) => a == b,
7872                        (Value::Bool(a), Value::Bool(b)) => a == b,
7873                        (Value::None, Value::None) => true,
7874                        _ => false,
7875                    });
7876                    Ok(Value::Int(idx.map(|i| i as i64).unwrap_or(-1)))
7877                }
7878                "slice" => {
7879                    if args.len() < 2 {
7880                        return Err(runtime_err_s("slice() expects start and end"));
7881                    }
7882                    let start = match &args[0] {
7883                        Value::Int(n) => *n as usize,
7884                        _ => return Err(runtime_err_s("slice() expects integers")),
7885                    };
7886                    let end = match &args[1] {
7887                        Value::Int(n) => *n as usize,
7888                        _ => return Err(runtime_err_s("slice() expects integers")),
7889                    };
7890                    let end = end.min(items.len());
7891                    let start = start.min(end);
7892                    Ok(Value::List(items[start..end].to_vec()))
7893                }
7894                "flat_map" => {
7895                    if args.is_empty() {
7896                        return Err(runtime_err_s("flat_map() expects a function"));
7897                    }
7898                    let func = &args[0];
7899                    let mut result = Vec::new();
7900                    for item in items {
7901                        let val = self.call_function(func, std::slice::from_ref(item))?;
7902                        match val {
7903                            Value::List(sub) => result.extend(sub),
7904                            other => result.push(other),
7905                        }
7906                    }
7907                    Ok(Value::List(result))
7908                }
7909                "find" => {
7910                    if let Some(func) = args.first() {
7911                        for item in items {
7912                            let result = self.call_function(func, std::slice::from_ref(item))?;
7913                            if result.is_truthy() {
7914                                return Ok(item.clone());
7915                            }
7916                        }
7917                        Ok(Value::None)
7918                    } else {
7919                        Err(runtime_err_s("find() expects a function"))
7920                    }
7921                }
7922                "sort_by" => {
7923                    if let Some(func) = args.first() {
7924                        let mut indexed: Vec<(Value, Value)> = Vec::new();
7925                        for item in items {
7926                            let key = self.call_function(func, std::slice::from_ref(item))?;
7927                            indexed.push((item.clone(), key));
7928                        }
7929                        indexed.sort_by(|(_, ka), (_, kb)| match (ka, kb) {
7930                            (Value::Int(a), Value::Int(b)) => a.cmp(b),
7931                            (Value::Float(a), Value::Float(b)) => {
7932                                a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
7933                            }
7934                            (Value::String(a), Value::String(b)) => a.cmp(b),
7935                            _ => std::cmp::Ordering::Equal,
7936                        });
7937                        Ok(Value::List(indexed.into_iter().map(|(v, _)| v).collect()))
7938                    } else {
7939                        Err(runtime_err_s("sort_by() expects a function"))
7940                    }
7941                }
7942                "group_by" => {
7943                    if let Some(func) = args.first() {
7944                        let mut groups: Vec<(String, Value)> = Vec::new();
7945                        for item in items {
7946                            let key = self.call_function(func, std::slice::from_ref(item))?;
7947                            let key_str = format!("{key}");
7948                            if let Some(entry) = groups.iter_mut().find(|(k, _)| k == &key_str) {
7949                                if let Value::List(ref mut list) = entry.1 {
7950                                    list.push(item.clone());
7951                                }
7952                            } else {
7953                                groups.push((key_str, Value::List(vec![item.clone()])));
7954                            }
7955                        }
7956                        Ok(Value::Map(groups))
7957                    } else {
7958                        Err(runtime_err_s("group_by() expects a function"))
7959                    }
7960                }
7961                "unique" => {
7962                    let mut result: Vec<Value> = Vec::new();
7963                    for item in items {
7964                        let exists = result.iter().any(|x| values_equal(x, item));
7965                        if !exists {
7966                            result.push(item.clone());
7967                        }
7968                    }
7969                    Ok(Value::List(result))
7970                }
7971                "flatten" => {
7972                    let mut result = Vec::new();
7973                    for item in items {
7974                        match item {
7975                            Value::List(sub) => result.extend(sub.clone()),
7976                            other => result.push(other.clone()),
7977                        }
7978                    }
7979                    Ok(Value::List(result))
7980                }
7981                "chunk" => {
7982                    let n = match args.first() {
7983                        Some(Value::Int(n)) => *n as usize,
7984                        _ => return Err(runtime_err_s("chunk() expects an integer")),
7985                    };
7986                    if n == 0 {
7987                        return Err(runtime_err_s("chunk() size must be > 0"));
7988                    }
7989                    let chunks: Vec<Value> =
7990                        items.chunks(n).map(|c| Value::List(c.to_vec())).collect();
7991                    Ok(Value::List(chunks))
7992                }
7993                "insert" => {
7994                    if args.len() < 2 {
7995                        return Err(runtime_err_s("insert() expects index and value"));
7996                    }
7997                    let idx = match &args[0] {
7998                        Value::Int(n) => *n as usize,
7999                        _ => return Err(runtime_err_s("insert() expects integer index")),
8000                    };
8001                    let mut new_items = items.clone();
8002                    if idx > new_items.len() {
8003                        return Err(runtime_err_s("insert() index out of bounds"));
8004                    }
8005                    new_items.insert(idx, args[1].clone());
8006                    Ok(Value::List(new_items))
8007                }
8008                "remove_at" => {
8009                    let idx = match args.first() {
8010                        Some(Value::Int(n)) => *n as usize,
8011                        _ => return Err(runtime_err_s("remove_at() expects an integer index")),
8012                    };
8013                    if idx >= items.len() {
8014                        return Err(runtime_err_s("remove_at() index out of bounds"));
8015                    }
8016                    let mut new_items = items.clone();
8017                    let removed = new_items.remove(idx);
8018                    Ok(Value::List(vec![removed, Value::List(new_items)]))
8019                }
8020                "is_empty" => Ok(Value::Bool(items.is_empty())),
8021                "sum" => {
8022                    let mut int_sum: i64 = 0;
8023                    let mut has_float = false;
8024                    let mut float_sum: f64 = 0.0;
8025                    for item in items {
8026                        match item {
8027                            Value::Int(n) => {
8028                                int_sum += n;
8029                                float_sum += *n as f64;
8030                            }
8031                            Value::Float(f) => {
8032                                has_float = true;
8033                                float_sum += f;
8034                            }
8035                            _ => return Err(runtime_err_s("sum() expects numeric list")),
8036                        }
8037                    }
8038                    if has_float {
8039                        Ok(Value::Float(float_sum))
8040                    } else {
8041                        Ok(Value::Int(int_sum))
8042                    }
8043                }
8044                "min" => {
8045                    if items.is_empty() {
8046                        return Err(runtime_err_s("min() on empty list"));
8047                    }
8048                    let mut result = items[0].clone();
8049                    for item in &items[1..] {
8050                        match (&result, item) {
8051                            (Value::Int(a), Value::Int(b)) => {
8052                                if b < a {
8053                                    result = item.clone();
8054                                }
8055                            }
8056                            (Value::Float(a), Value::Float(b)) => {
8057                                if b < a {
8058                                    result = item.clone();
8059                                }
8060                            }
8061                            _ => {}
8062                        }
8063                    }
8064                    Ok(result)
8065                }
8066                "max" => {
8067                    if items.is_empty() {
8068                        return Err(runtime_err_s("max() on empty list"));
8069                    }
8070                    let mut result = items[0].clone();
8071                    for item in &items[1..] {
8072                        match (&result, item) {
8073                            (Value::Int(a), Value::Int(b)) => {
8074                                if b > a {
8075                                    result = item.clone();
8076                                }
8077                            }
8078                            (Value::Float(a), Value::Float(b)) => {
8079                                if b > a {
8080                                    result = item.clone();
8081                                }
8082                            }
8083                            _ => {}
8084                        }
8085                    }
8086                    Ok(result)
8087                }
8088                "each" => {
8089                    if let Some(func) = args.first() {
8090                        for item in items {
8091                            self.call_function(func, std::slice::from_ref(item))?;
8092                        }
8093                        Ok(Value::None)
8094                    } else {
8095                        Err(runtime_err_s("each() expects a function"))
8096                    }
8097                }
8098                "zip" => {
8099                    if args.is_empty() {
8100                        return Err(runtime_err_s("zip() expects a list"));
8101                    }
8102                    if let Value::List(other) = &args[0] {
8103                        let result: Vec<Value> = items
8104                            .iter()
8105                            .zip(other.iter())
8106                            .map(|(a, b)| Value::List(vec![a.clone(), b.clone()]))
8107                            .collect();
8108                        Ok(Value::List(result))
8109                    } else {
8110                        Err(runtime_err_s("zip() expects a list"))
8111                    }
8112                }
8113                "join" => {
8114                    let sep = match args.first() {
8115                        Some(Value::String(s)) => s.clone(),
8116                        _ => ",".to_string(),
8117                    };
8118                    let parts: Vec<String> = items.iter().map(|v| format!("{v}")).collect();
8119                    Ok(Value::String(parts.join(&sep)))
8120                }
8121                _ => Err(runtime_err(format!("List has no method `{method}`"))),
8122            };
8123        }
8124
8125        // Map methods
8126        if let Value::Map(pairs) = obj {
8127            return match method {
8128                "len" => Ok(Value::Int(pairs.len() as i64)),
8129                "keys" => Ok(Value::List(
8130                    pairs
8131                        .iter()
8132                        .map(|(k, _)| Value::String(k.clone()))
8133                        .collect(),
8134                )),
8135                "values" => Ok(Value::List(pairs.iter().map(|(_, v)| v.clone()).collect())),
8136                "contains_key" => {
8137                    if args.is_empty() {
8138                        return Err(runtime_err_s("contains_key() expects a key"));
8139                    }
8140                    if let Value::String(key) = &args[0] {
8141                        Ok(Value::Bool(pairs.iter().any(|(k, _)| k == key)))
8142                    } else {
8143                        Err(runtime_err_s("contains_key() expects a string key"))
8144                    }
8145                }
8146                "remove" => {
8147                    if args.is_empty() {
8148                        return Err(runtime_err_s("remove() expects a key"));
8149                    }
8150                    if let Value::String(key) = &args[0] {
8151                        let new_pairs: Vec<(String, Value)> =
8152                            pairs.iter().filter(|(k, _)| k != key).cloned().collect();
8153                        Ok(Value::Map(new_pairs))
8154                    } else {
8155                        Err(runtime_err_s("remove() expects a string key"))
8156                    }
8157                }
8158                "get" => {
8159                    if args.is_empty() {
8160                        return Err(runtime_err_s("get() expects a key"));
8161                    }
8162                    if let Value::String(key) = &args[0] {
8163                        let val = pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v.clone());
8164                        Ok(val.unwrap_or_else(|| args.get(1).cloned().unwrap_or(Value::None)))
8165                    } else {
8166                        Err(runtime_err_s("get() expects a string key"))
8167                    }
8168                }
8169                "merge" => {
8170                    if args.is_empty() {
8171                        return Err(runtime_err_s("merge() expects a map"));
8172                    }
8173                    if let Value::Map(other) = &args[0] {
8174                        let mut result = pairs.clone();
8175                        for (k, v) in other {
8176                            if let Some(entry) = result.iter_mut().find(|(ek, _)| ek == k) {
8177                                entry.1 = v.clone();
8178                            } else {
8179                                result.push((k.clone(), v.clone()));
8180                            }
8181                        }
8182                        Ok(Value::Map(result))
8183                    } else {
8184                        Err(runtime_err_s("merge() expects a map"))
8185                    }
8186                }
8187                "entries" => {
8188                    let result: Vec<Value> = pairs
8189                        .iter()
8190                        .map(|(k, v)| Value::List(vec![Value::String(k.clone()), v.clone()]))
8191                        .collect();
8192                    Ok(Value::List(result))
8193                }
8194                "map_values" => {
8195                    if let Some(func) = args.first() {
8196                        let mut result = Vec::new();
8197                        for (k, v) in pairs {
8198                            let new_v = self.call_function(func, std::slice::from_ref(v))?;
8199                            result.push((k.clone(), new_v));
8200                        }
8201                        Ok(Value::Map(result))
8202                    } else {
8203                        Err(runtime_err_s("map_values() expects a function"))
8204                    }
8205                }
8206                "filter" => {
8207                    if let Some(func) = args.first() {
8208                        let mut result = Vec::new();
8209                        for (k, v) in pairs {
8210                            let pair = Value::List(vec![Value::String(k.clone()), v.clone()]);
8211                            if self
8212                                .call_function(func, std::slice::from_ref(&pair))?
8213                                .is_truthy()
8214                            {
8215                                result.push((k.clone(), v.clone()));
8216                            }
8217                        }
8218                        Ok(Value::Map(result))
8219                    } else {
8220                        Err(runtime_err_s("filter() expects a function"))
8221                    }
8222                }
8223                "set" => {
8224                    if args.len() < 2 {
8225                        return Err(runtime_err_s("set() expects key and value"));
8226                    }
8227                    if let Value::String(key) = &args[0] {
8228                        let mut result = pairs.clone();
8229                        if let Some(entry) = result.iter_mut().find(|(k, _)| k == key) {
8230                            entry.1 = args[1].clone();
8231                        } else {
8232                            result.push((key.clone(), args[1].clone()));
8233                        }
8234                        Ok(Value::Map(result))
8235                    } else {
8236                        Err(runtime_err_s("set() expects a string key"))
8237                    }
8238                }
8239                "is_empty" => Ok(Value::Bool(pairs.is_empty())),
8240                _ => Err(runtime_err(format!("Map has no method `{method}`"))),
8241            };
8242        }
8243
8244        // Set methods
8245        if let Value::Set(items) = obj {
8246            return match method {
8247                "len" => Ok(Value::Int(items.len() as i64)),
8248                "contains" => {
8249                    if args.is_empty() {
8250                        return Err(runtime_err_s("contains() expects a value"));
8251                    }
8252                    Ok(Value::Bool(items.iter().any(|x| values_equal(x, &args[0]))))
8253                }
8254                "add" => {
8255                    if args.is_empty() {
8256                        return Err(runtime_err_s("add() expects a value"));
8257                    }
8258                    let mut new_items = items.clone();
8259                    if !new_items.iter().any(|x| values_equal(x, &args[0])) {
8260                        new_items.push(args[0].clone());
8261                    }
8262                    Ok(Value::Set(new_items))
8263                }
8264                "remove" => {
8265                    if args.is_empty() {
8266                        return Err(runtime_err_s("remove() expects a value"));
8267                    }
8268                    let new_items: Vec<Value> = items
8269                        .iter()
8270                        .filter(|x| !values_equal(x, &args[0]))
8271                        .cloned()
8272                        .collect();
8273                    Ok(Value::Set(new_items))
8274                }
8275                "to_list" => Ok(Value::List(items.clone())),
8276                "union" => {
8277                    if args.is_empty() {
8278                        return Err(runtime_err_s("union() expects a set"));
8279                    }
8280                    if let Value::Set(b) = &args[0] {
8281                        let mut result = items.clone();
8282                        for item in b {
8283                            if !result.iter().any(|x| values_equal(x, item)) {
8284                                result.push(item.clone());
8285                            }
8286                        }
8287                        Ok(Value::Set(result))
8288                    } else {
8289                        Err(runtime_err_s("union() expects a set"))
8290                    }
8291                }
8292                "intersection" => {
8293                    if args.is_empty() {
8294                        return Err(runtime_err_s("intersection() expects a set"));
8295                    }
8296                    if let Value::Set(b) = &args[0] {
8297                        let result: Vec<Value> = items
8298                            .iter()
8299                            .filter(|x| b.iter().any(|y| values_equal(x, y)))
8300                            .cloned()
8301                            .collect();
8302                        Ok(Value::Set(result))
8303                    } else {
8304                        Err(runtime_err_s("intersection() expects a set"))
8305                    }
8306                }
8307                "difference" => {
8308                    if args.is_empty() {
8309                        return Err(runtime_err_s("difference() expects a set"));
8310                    }
8311                    if let Value::Set(b) = &args[0] {
8312                        let result: Vec<Value> = items
8313                            .iter()
8314                            .filter(|x| !b.iter().any(|y| values_equal(x, y)))
8315                            .cloned()
8316                            .collect();
8317                        Ok(Value::Set(result))
8318                    } else {
8319                        Err(runtime_err_s("difference() expects a set"))
8320                    }
8321                }
8322                _ => Err(runtime_err(format!("Set has no method `{method}`"))),
8323            };
8324        }
8325
8326        // Generator method dispatch
8327        if let Value::Generator(gen_arc) = obj {
8328            let gen_arc = gen_arc.clone();
8329            return match method {
8330                "next" => self.interpreter_next(&gen_arc),
8331                "collect" => {
8332                    let mut items = Vec::new();
8333                    loop {
8334                        let val = self.interpreter_next(&gen_arc)?;
8335                        if matches!(val, Value::None) {
8336                            break;
8337                        }
8338                        items.push(val);
8339                    }
8340                    Ok(Value::List(items))
8341                }
8342                _ => Err(runtime_err(format!("Generator has no method `{method}`"))),
8343            };
8344        }
8345
8346        // Module method dispatch (for aliased use: m.compute())
8347        if let Value::Module { name, exports } = obj {
8348            if let Some(func) = exports.get(method) {
8349                return self.call_function(func, args);
8350            } else {
8351                return Err(runtime_err(format!(
8352                    "Module '{}' has no export '{}'",
8353                    name, method
8354                )));
8355            }
8356        }
8357
8358        // Struct/impl method dispatch
8359        if let Value::StructInstance { type_name, .. } = obj
8360            && let Some(methods) = self.method_table.get(type_name)
8361            && let Some(func) = methods.get(method)
8362        {
8363            let func = func.clone();
8364            // Prepend self to args
8365            let mut all_args = vec![obj.clone()];
8366            all_args.extend_from_slice(args);
8367            return self.call_function(&func, &all_args);
8368        }
8369
8370        // Python method dispatch
8371        #[cfg(feature = "python")]
8372        if let Value::PyObject(wrapper) = obj {
8373            return interp_py_call_method(wrapper, method, args);
8374        }
8375
8376        Err(runtime_err(format!(
8377            "No method `{method}` on {}",
8378            obj.type_name()
8379        )))
8380    }
8381
8382    /// Execute a use statement (placeholder — full impl in Step 5)
8383    fn exec_use(&mut self, item: &tl_ast::UseItem) -> Result<Signal, TlError> {
8384        use tl_ast::UseItem;
8385        match item {
8386            UseItem::Single(path) | UseItem::Wildcard(path) | UseItem::Aliased(path, _) => {
8387                let file_path = self.resolve_use_path(path)?;
8388                let alias = match item {
8389                    UseItem::Aliased(_, alias) => Some(alias.as_str()),
8390                    // Single and Wildcard both merge exports into scope
8391                    _ => None,
8392                };
8393                self.exec_import(&file_path, alias)
8394            }
8395            UseItem::Group(prefix, names) => {
8396                let file_path = self.resolve_use_path(prefix)?;
8397                // Import the module, then pick specific items
8398                self.exec_import(&file_path, None)?;
8399                // Items are already imported by wildcard for now
8400                let _ = names;
8401                Ok(Signal::None)
8402            }
8403        }
8404    }
8405
8406    /// Resolve a dot-path to a file path for use statements
8407    fn resolve_use_path(&self, segments: &[String]) -> Result<String, TlError> {
8408        // Reject path traversal attempts
8409        if segments.iter().any(|s| s == "..") {
8410            return Err(TlError::Runtime(tl_errors::RuntimeError {
8411                message: "Import paths cannot contain '..'".to_string(),
8412                span: None,
8413                stack_trace: vec![],
8414            }));
8415        }
8416
8417        let base_dir = if let Some(ref fp) = self.file_path {
8418            std::path::Path::new(fp)
8419                .parent()
8420                .unwrap_or(std::path::Path::new("."))
8421                .to_path_buf()
8422        } else {
8423            std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."))
8424        };
8425
8426        // Try: segments joined as path with .tl extension
8427        // e.g., ["data", "transforms"] -> "data/transforms.tl"
8428        let rel_path = segments.join("/");
8429
8430        // Try file module first
8431        let file_path = base_dir.join(format!("{rel_path}.tl"));
8432        if file_path.exists() {
8433            return Ok(file_path.to_string_lossy().to_string());
8434        }
8435
8436        // Try directory module (mod.tl)
8437        let dir_path = base_dir.join(&rel_path).join("mod.tl");
8438        if dir_path.exists() {
8439            return Ok(dir_path.to_string_lossy().to_string());
8440        }
8441
8442        // If multi-segment, try parent as file module
8443        if segments.len() > 1 {
8444            let parent = &segments[..segments.len() - 1];
8445            let parent_path = parent.join("/");
8446            let parent_file = base_dir.join(format!("{parent_path}.tl"));
8447            if parent_file.exists() {
8448                return Ok(parent_file.to_string_lossy().to_string());
8449            }
8450            let parent_dir = base_dir.join(&parent_path).join("mod.tl");
8451            if parent_dir.exists() {
8452                return Ok(parent_dir.to_string_lossy().to_string());
8453            }
8454        }
8455
8456        // Package import fallback: first segment as package name
8457        let pkg_name = &segments[0];
8458        let pkg_name_hyphen = pkg_name.replace('_', "-");
8459        let pkg_root = self
8460            .package_roots
8461            .get(pkg_name.as_str())
8462            .or_else(|| self.package_roots.get(&pkg_name_hyphen));
8463
8464        if let Some(root) = pkg_root {
8465            let remaining: Vec<&str> = segments[1..].iter().map(|s| s.as_str()).collect();
8466            if let Some(path) = resolve_package_file_interp(root, &remaining) {
8467                return Ok(path);
8468            }
8469        }
8470
8471        Err(TlError::Runtime(tl_errors::RuntimeError {
8472            message: format!("Module not found: {}", segments.join(".")),
8473            span: None,
8474            stack_trace: vec![],
8475        }))
8476    }
8477
8478    /// Execute an import statement
8479    fn exec_import(&mut self, path: &str, alias: Option<&str>) -> Result<Signal, TlError> {
8480        // Resolve path relative to current file
8481        let resolved = if let Some(ref base) = self.file_path {
8482            let base_dir = std::path::Path::new(base)
8483                .parent()
8484                .unwrap_or(std::path::Path::new("."));
8485            base_dir.join(path).to_string_lossy().to_string()
8486        } else {
8487            path.to_string()
8488        };
8489
8490        // Circular dependency detection
8491        if self.importing_files.contains(&resolved) {
8492            return Err(TlError::Runtime(RuntimeError {
8493                message: format!("Circular import detected: {resolved}"),
8494                span: None,
8495                stack_trace: vec![],
8496            }));
8497        }
8498
8499        // Check cache
8500        if let Some(exports) = self.module_cache.get(&resolved) {
8501            if let Some(alias) = alias {
8502                self.env.set(
8503                    alias.to_string(),
8504                    Value::Module {
8505                        name: alias.to_string(),
8506                        exports: exports.clone(),
8507                    },
8508                );
8509            } else {
8510                for (k, v) in exports {
8511                    self.env.set(k.clone(), v.clone());
8512                }
8513            }
8514            return Ok(Signal::None);
8515        }
8516
8517        // Read and parse file
8518        let source = std::fs::read_to_string(&resolved)
8519            .map_err(|e| runtime_err(format!("Cannot read '{resolved}': {e}")))?;
8520        let program = tl_parser::parse(&source)
8521            .map_err(|e| runtime_err(format!("Parse error in '{resolved}': {e}")))?;
8522
8523        // Execute in a fresh interpreter with shared method table
8524        self.importing_files.insert(resolved.clone());
8525        let mut sub_interp = Interpreter::new();
8526        sub_interp.file_path = Some(resolved.clone());
8527        sub_interp.importing_files = self.importing_files.clone();
8528        sub_interp.package_roots = self.package_roots.clone();
8529        sub_interp.project_root = self.project_root.clone();
8530        sub_interp.execute(&program)?;
8531        self.importing_files.remove(&resolved);
8532
8533        // Collect exports (all top-level bindings)
8534        let exports: HashMap<String, Value> = sub_interp.env.scopes[0].clone();
8535
8536        // Merge method tables from imported module
8537        for (type_name, methods) in &sub_interp.method_table {
8538            let entry = self.method_table.entry(type_name.clone()).or_default();
8539            for (name, func) in methods {
8540                entry.insert(name.clone(), func.clone());
8541            }
8542        }
8543
8544        // Cache
8545        self.module_cache.insert(resolved, exports.clone());
8546
8547        // Inject into current scope
8548        if let Some(alias) = alias {
8549            self.env.set(
8550                alias.to_string(),
8551                Value::Module {
8552                    name: alias.to_string(),
8553                    exports,
8554                },
8555            );
8556        } else {
8557            for (k, v) in exports {
8558                // Don't import builtins
8559                if !matches!(v, Value::Builtin(_)) {
8560                    self.env.set(k, v);
8561                }
8562            }
8563        }
8564
8565        Ok(Signal::None)
8566    }
8567
8568    /// Simple string interpolation: replace {expr} with evaluated value
8569    fn interpolate_string(&mut self, s: &str) -> Result<String, TlError> {
8570        let mut result = String::new();
8571        let mut chars = s.chars().peekable();
8572        while let Some(ch) = chars.next() {
8573            if ch == '{' {
8574                let mut expr_str = String::new();
8575                let mut depth = 1;
8576                for c in chars.by_ref() {
8577                    if c == '{' {
8578                        depth += 1;
8579                    } else if c == '}' {
8580                        depth -= 1;
8581                        if depth == 0 {
8582                            break;
8583                        }
8584                    }
8585                    expr_str.push(c);
8586                }
8587                // Look up the variable in the environment
8588                if let Some(val) = self.env.get(&expr_str) {
8589                    result.push_str(&format!("{val}"));
8590                } else {
8591                    result.push('{');
8592                    result.push_str(&expr_str);
8593                    result.push('}');
8594                }
8595            } else if ch == '\\' {
8596                // Handle escape sequences
8597                match chars.next() {
8598                    Some('n') => result.push('\n'),
8599                    Some('t') => result.push('\t'),
8600                    Some('\\') => result.push('\\'),
8601                    Some('"') => result.push('"'),
8602                    Some(c) => {
8603                        result.push('\\');
8604                        result.push(c);
8605                    }
8606                    None => result.push('\\'),
8607                }
8608            } else {
8609                result.push(ch);
8610            }
8611        }
8612        Ok(result)
8613    }
8614
8615    // ── Table-aware pipe evaluation ─────────────────────────
8616
8617    /// Evaluate `table |> operation(args)` — dispatches to table operations.
8618    fn eval_table_pipe(&mut self, df: DataFrame, right: &Expr) -> Result<Value, TlError> {
8619        match right {
8620            Expr::Call { function, args } => {
8621                let fname = match function.as_ref() {
8622                    Expr::Ident(name) => name.as_str(),
8623                    _ => {
8624                        // Fall through to regular call with table as first arg
8625                        let func = self.eval_expr(function)?;
8626                        let mut all_args = vec![Value::Table(TlTable { df })];
8627                        for arg in args {
8628                            all_args.push(self.eval_expr(arg)?);
8629                        }
8630                        return self.call_function(&func, &all_args);
8631                    }
8632                };
8633                match fname {
8634                    "filter" => self.table_filter(df, args),
8635                    "select" => self.table_select(df, args),
8636                    "sort" => self.table_sort(df, args),
8637                    "with" => self.table_with(df, args),
8638                    "aggregate" => self.table_aggregate(df, args),
8639                    "join" => self.table_join(df, args),
8640                    "head" => self.table_limit(df, args),
8641                    "limit" => self.table_limit(df, args),
8642                    "collect" => {
8643                        let batches = self.engine().collect(df).map_err(runtime_err)?;
8644                        let formatted =
8645                            DataEngine::format_batches(&batches).map_err(runtime_err)?;
8646                        Ok(Value::String(formatted))
8647                    }
8648                    "show" => {
8649                        let limit = match args.first() {
8650                            Some(expr) => {
8651                                let val = self.eval_expr(expr)?;
8652                                match val {
8653                                    Value::Int(n) => n as usize,
8654                                    _ => 20,
8655                                }
8656                            }
8657                            None => 20,
8658                        };
8659                        let limited = df
8660                            .limit(0, Some(limit))
8661                            .map_err(|e| runtime_err(format!("{e}")))?;
8662                        let batches = self.engine().collect(limited).map_err(runtime_err)?;
8663                        let formatted =
8664                            DataEngine::format_batches(&batches).map_err(runtime_err)?;
8665                        println!("{formatted}");
8666                        self.output.push(formatted);
8667                        Ok(Value::None)
8668                    }
8669                    "describe" => {
8670                        let schema = df.schema();
8671                        let mut lines = Vec::new();
8672                        lines.push("Columns:".to_string());
8673                        for field in schema.fields() {
8674                            lines.push(format!("  {}: {}", field.name(), field.data_type()));
8675                        }
8676                        let output = lines.join("\n");
8677                        println!("{output}");
8678                        self.output.push(output.clone());
8679                        Ok(Value::String(output))
8680                    }
8681                    "write_csv" => {
8682                        if args.len() != 1 {
8683                            return Err(runtime_err(
8684                                "write_csv() expects 1 argument (path)".into(),
8685                            ));
8686                        }
8687                        let path = match self.eval_expr(&args[0])? {
8688                            Value::String(s) => s,
8689                            _ => {
8690                                return Err(runtime_err(
8691                                    "write_csv() path must be a string".into(),
8692                                ));
8693                            }
8694                        };
8695                        self.engine().write_csv(df, &path).map_err(runtime_err)?;
8696                        Ok(Value::None)
8697                    }
8698                    "write_parquet" => {
8699                        if args.len() != 1 {
8700                            return Err(runtime_err(
8701                                "write_parquet() expects 1 argument (path)".into(),
8702                            ));
8703                        }
8704                        let path = match self.eval_expr(&args[0])? {
8705                            Value::String(s) => s,
8706                            _ => {
8707                                return Err(runtime_err(
8708                                    "write_parquet() path must be a string".into(),
8709                                ));
8710                            }
8711                        };
8712                        self.engine()
8713                            .write_parquet(df, &path)
8714                            .map_err(runtime_err)?;
8715                        Ok(Value::None)
8716                    }
8717                    // Phase 15: Data quality pipe operations
8718                    "fill_null" => {
8719                        if args.is_empty() {
8720                            return Err(runtime_err(
8721                                "fill_null() expects (column, [strategy/value])".into(),
8722                            ));
8723                        }
8724                        let column = match self.eval_expr(&args[0])? {
8725                            Value::String(s) => s,
8726                            _ => {
8727                                return Err(runtime_err(
8728                                    "fill_null() column must be a string".into(),
8729                                ));
8730                            }
8731                        };
8732                        if args.len() >= 2 {
8733                            let val = self.eval_expr(&args[1])?;
8734                            match val {
8735                                Value::String(s) => {
8736                                    let fill_val = if args.len() >= 3 {
8737                                        match self.eval_expr(&args[2])? {
8738                                            Value::Int(n) => Some(n as f64),
8739                                            Value::Float(f) => Some(f),
8740                                            _ => None,
8741                                        }
8742                                    } else {
8743                                        None
8744                                    };
8745                                    let result = self
8746                                        .engine()
8747                                        .fill_null(df, &column, &s, fill_val)
8748                                        .map_err(runtime_err)?;
8749                                    Ok(Value::Table(TlTable { df: result }))
8750                                }
8751                                Value::Int(n) => {
8752                                    let result = self
8753                                        .engine()
8754                                        .fill_null(df, &column, "value", Some(n as f64))
8755                                        .map_err(runtime_err)?;
8756                                    Ok(Value::Table(TlTable { df: result }))
8757                                }
8758                                Value::Float(f) => {
8759                                    let result = self
8760                                        .engine()
8761                                        .fill_null(df, &column, "value", Some(f))
8762                                        .map_err(runtime_err)?;
8763                                    Ok(Value::Table(TlTable { df: result }))
8764                                }
8765                                _ => Err(runtime_err(
8766                                    "fill_null() second arg must be a strategy or value".into(),
8767                                )),
8768                            }
8769                        } else {
8770                            let result = self
8771                                .engine()
8772                                .fill_null(df, &column, "zero", None)
8773                                .map_err(runtime_err)?;
8774                            Ok(Value::Table(TlTable { df: result }))
8775                        }
8776                    }
8777                    "drop_null" => {
8778                        if args.is_empty() {
8779                            return Err(runtime_err("drop_null() expects (column)".into()));
8780                        }
8781                        let column = match self.eval_expr(&args[0])? {
8782                            Value::String(s) => s,
8783                            _ => {
8784                                return Err(runtime_err(
8785                                    "drop_null() column must be a string".into(),
8786                                ));
8787                            }
8788                        };
8789                        let result = self.engine().drop_null(df, &column).map_err(runtime_err)?;
8790                        Ok(Value::Table(TlTable { df: result }))
8791                    }
8792                    "dedup" => {
8793                        let columns: Vec<String> = args
8794                            .iter()
8795                            .filter_map(|a| match self.eval_expr(a) {
8796                                Ok(Value::String(s)) => Some(s),
8797                                _ => None,
8798                            })
8799                            .collect();
8800                        let result = self.engine().dedup(df, &columns).map_err(runtime_err)?;
8801                        Ok(Value::Table(TlTable { df: result }))
8802                    }
8803                    "clamp" => {
8804                        if args.len() < 3 {
8805                            return Err(runtime_err("clamp() expects (column, min, max)".into()));
8806                        }
8807                        let column = match self.eval_expr(&args[0])? {
8808                            Value::String(s) => s,
8809                            _ => return Err(runtime_err("clamp() column must be a string".into())),
8810                        };
8811                        let min_val = match self.eval_expr(&args[1])? {
8812                            Value::Int(n) => n as f64,
8813                            Value::Float(f) => f,
8814                            _ => return Err(runtime_err("clamp() min must be a number".into())),
8815                        };
8816                        let max_val = match self.eval_expr(&args[2])? {
8817                            Value::Int(n) => n as f64,
8818                            Value::Float(f) => f,
8819                            _ => return Err(runtime_err("clamp() max must be a number".into())),
8820                        };
8821                        let result = self
8822                            .engine()
8823                            .clamp(df, &column, min_val, max_val)
8824                            .map_err(runtime_err)?;
8825                        Ok(Value::Table(TlTable { df: result }))
8826                    }
8827                    "data_profile" => {
8828                        let result = self.engine().data_profile(df).map_err(runtime_err)?;
8829                        Ok(Value::Table(TlTable { df: result }))
8830                    }
8831                    "row_count" => {
8832                        let count = self.engine().row_count(df).map_err(runtime_err)?;
8833                        Ok(Value::Int(count))
8834                    }
8835                    "null_rate" => {
8836                        if args.is_empty() {
8837                            return Err(runtime_err("null_rate() expects (column)".into()));
8838                        }
8839                        let column = match self.eval_expr(&args[0])? {
8840                            Value::String(s) => s,
8841                            _ => {
8842                                return Err(runtime_err(
8843                                    "null_rate() column must be a string".into(),
8844                                ));
8845                            }
8846                        };
8847                        let rate = self.engine().null_rate(df, &column).map_err(runtime_err)?;
8848                        Ok(Value::Float(rate))
8849                    }
8850                    "is_unique" => {
8851                        if args.is_empty() {
8852                            return Err(runtime_err("is_unique() expects (column)".into()));
8853                        }
8854                        let column = match self.eval_expr(&args[0])? {
8855                            Value::String(s) => s,
8856                            _ => {
8857                                return Err(runtime_err(
8858                                    "is_unique() column must be a string".into(),
8859                                ));
8860                            }
8861                        };
8862                        let unique = self.engine().is_unique(df, &column).map_err(runtime_err)?;
8863                        Ok(Value::Bool(unique))
8864                    }
8865                    // Phase F2: Window functions
8866                    "window" => {
8867                        use tl_data::datafusion::logical_expr::{
8868                            WindowFrame, WindowFunctionDefinition,
8869                            expr::{Sort as DfSort, WindowFunction as WinFunc},
8870                        };
8871                        if args.is_empty() {
8872                            return Err(runtime_err("window() expects named arguments: fn, partition_by, order_by, alias".into()));
8873                        }
8874                        let mut win_fn_name = String::new();
8875                        let mut partition_by_cols: Vec<String> = Vec::new();
8876                        let mut order_by_cols: Vec<String> = Vec::new();
8877                        let mut alias_name = String::new();
8878                        let mut win_args: Vec<String> = Vec::new();
8879                        let mut descending = false;
8880
8881                        for arg in args {
8882                            if let Expr::NamedArg { name, value } = arg {
8883                                match name.as_str() {
8884                                    "fn" => {
8885                                        if let Value::String(s) = self.eval_expr(value)? {
8886                                            win_fn_name = s;
8887                                        }
8888                                    }
8889                                    "partition_by" => match self.eval_expr(value)? {
8890                                        Value::List(items) => {
8891                                            for item in &items {
8892                                                if let Value::String(s) = item {
8893                                                    partition_by_cols.push(s.clone());
8894                                                }
8895                                            }
8896                                        }
8897                                        Value::String(s) => partition_by_cols.push(s),
8898                                        _ => {}
8899                                    },
8900                                    "order_by" => match self.eval_expr(value)? {
8901                                        Value::List(items) => {
8902                                            for item in &items {
8903                                                if let Value::String(s) = item {
8904                                                    order_by_cols.push(s.clone());
8905                                                }
8906                                            }
8907                                        }
8908                                        Value::String(s) => order_by_cols.push(s),
8909                                        _ => {}
8910                                    },
8911                                    "alias" | "as" => {
8912                                        if let Value::String(s) = self.eval_expr(value)? {
8913                                            alias_name = s;
8914                                        }
8915                                    }
8916                                    "args" => match self.eval_expr(value)? {
8917                                        Value::List(items) => {
8918                                            for item in &items {
8919                                                if let Value::String(s) = item {
8920                                                    win_args.push(s.clone());
8921                                                } else {
8922                                                    win_args.push(format!("{item}"));
8923                                                }
8924                                            }
8925                                        }
8926                                        Value::String(s) => win_args.push(s),
8927                                        _ => {
8928                                            let v = self.eval_expr(value)?;
8929                                            win_args.push(format!("{v}"));
8930                                        }
8931                                    },
8932                                    "desc" => {
8933                                        if let Value::Bool(b) = self.eval_expr(value)? {
8934                                            descending = b;
8935                                        }
8936                                    }
8937                                    _ => {}
8938                                }
8939                            }
8940                        }
8941
8942                        if win_fn_name.is_empty() {
8943                            return Err(runtime_err("window() requires fn: parameter".into()));
8944                        }
8945                        if alias_name.is_empty() {
8946                            alias_name = win_fn_name.clone();
8947                        }
8948
8949                        let session = self.engine().session_ctx();
8950                        let win_udf = match win_fn_name.as_str() {
8951                            "rank" => session.udwf("rank"),
8952                            "dense_rank" => session.udwf("dense_rank"),
8953                            "row_number" => session.udwf("row_number"),
8954                            "percent_rank" => session.udwf("percent_rank"),
8955                            "cume_dist" => session.udwf("cume_dist"),
8956                            "ntile" => session.udwf("ntile"),
8957                            "lag" => session.udwf("lag"),
8958                            "lead" => session.udwf("lead"),
8959                            "first_value" => session.udwf("first_value"),
8960                            "last_value" => session.udwf("last_value"),
8961                            _ => {
8962                                return Err(runtime_err(format!(
8963                                    "Unknown window function: {win_fn_name}"
8964                                )));
8965                            }
8966                        }
8967                        .map_err(|e| {
8968                            runtime_err(format!(
8969                                "Window function '{win_fn_name}' not available: {e}"
8970                            ))
8971                        })?;
8972
8973                        let fun = WindowFunctionDefinition::WindowUDF(win_udf);
8974                        let func_args: Vec<tl_data::datafusion::prelude::Expr> = win_args
8975                            .iter()
8976                            .map(|a| {
8977                                if let Ok(n) = a.parse::<i64>() {
8978                                    lit(n)
8979                                } else {
8980                                    col(a.as_str())
8981                                }
8982                            })
8983                            .collect();
8984
8985                        let partition_exprs: Vec<tl_data::datafusion::prelude::Expr> =
8986                            partition_by_cols.iter().map(|c| col(c.as_str())).collect();
8987                        let order_exprs: Vec<DfSort> = order_by_cols
8988                            .iter()
8989                            .map(|c| DfSort::new(col(c.as_str()), !descending, true))
8990                            .collect();
8991
8992                        let has_order = !order_exprs.is_empty();
8993                        let win_expr =
8994                            tl_data::datafusion::prelude::Expr::WindowFunction(WinFunc {
8995                                fun,
8996                                args: func_args,
8997                                partition_by: partition_exprs,
8998                                order_by: order_exprs,
8999                                window_frame: WindowFrame::new(if has_order {
9000                                    Some(true)
9001                                } else {
9002                                    None
9003                                }),
9004                                null_treatment: None,
9005                            })
9006                            .alias(&alias_name);
9007
9008                        let schema = df.schema();
9009                        let mut select_exprs: Vec<tl_data::datafusion::prelude::Expr> = schema
9010                            .fields()
9011                            .iter()
9012                            .map(|f| col(f.name().as_str()))
9013                            .collect();
9014                        select_exprs.push(win_expr);
9015
9016                        let result_df = df
9017                            .select(select_exprs)
9018                            .map_err(|e| runtime_err(format!("Window function error: {e}")))?;
9019                        Ok(Value::Table(TlTable { df: result_df }))
9020                    }
9021                    // Phase F3: Union
9022                    "union" => {
9023                        if args.is_empty() {
9024                            return Err(runtime_err("union() expects a table argument".into()));
9025                        }
9026                        let right_table = self.eval_expr(&args[0])?;
9027                        let right_df = match right_table {
9028                            Value::Table(t) => t.df,
9029                            _ => {
9030                                return Err(runtime_err("union() argument must be a table".into()));
9031                            }
9032                        };
9033                        let result_df = df
9034                            .union(right_df)
9035                            .map_err(|e| runtime_err(format!("Union error: {e}")))?;
9036                        Ok(Value::Table(TlTable { df: result_df }))
9037                    }
9038                    // Unknown table op: fall through to regular call
9039                    _ => {
9040                        let func = self.env.get(fname).cloned().ok_or_else(|| {
9041                            runtime_err(format!("Unknown table operation: `{fname}`"))
9042                        })?;
9043                        let mut all_args = vec![Value::Table(TlTable { df })];
9044                        for arg in args {
9045                            all_args.push(self.eval_expr(arg)?);
9046                        }
9047                        self.call_function(&func, &all_args)
9048                    }
9049                }
9050            }
9051            Expr::Ident(name) => {
9052                let func = self
9053                    .env
9054                    .get(name)
9055                    .cloned()
9056                    .ok_or_else(|| runtime_err(format!("Unknown table operation: `{name}`")))?;
9057                self.call_function(&func, &[Value::Table(TlTable { df })])
9058            }
9059            _ => Err(runtime_err(
9060                "Right side of |> must be a function call".into(),
9061            )),
9062        }
9063    }
9064
9065    /// Build a TranslateContext from current interpreter locals.
9066    fn build_translate_context(&self) -> TranslateContext {
9067        let mut ctx = TranslateContext::new();
9068        for scope in &self.env.scopes {
9069            for (name, val) in scope {
9070                let local = match val {
9071                    Value::Int(n) => Some(LocalValue::Int(*n)),
9072                    Value::Float(f) => Some(LocalValue::Float(*f)),
9073                    Value::String(s) => Some(LocalValue::String(s.clone())),
9074                    Value::Bool(b) => Some(LocalValue::Bool(*b)),
9075                    _ => None,
9076                };
9077                if let Some(local) = local {
9078                    ctx.locals.insert(name.clone(), local);
9079                }
9080            }
9081        }
9082        ctx
9083    }
9084
9085    /// `table |> filter(predicate)`
9086    fn table_filter(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9087        if args.len() != 1 {
9088            return Err(runtime_err(
9089                "filter() expects 1 argument (predicate)".into(),
9090            ));
9091        }
9092        let ctx = self.build_translate_context();
9093        let pred = translate_expr(&args[0], &ctx).map_err(runtime_err)?;
9094        let filtered = df.filter(pred).map_err(|e| runtime_err(format!("{e}")))?;
9095        Ok(Value::Table(TlTable { df: filtered }))
9096    }
9097
9098    /// `table |> select(col1, col2, name: expr)`
9099    fn table_select(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9100        if args.is_empty() {
9101            return Err(runtime_err("select() expects at least 1 argument".into()));
9102        }
9103        let ctx = self.build_translate_context();
9104        let mut select_exprs = Vec::new();
9105        for arg in args {
9106            match arg {
9107                Expr::Ident(name) => {
9108                    select_exprs.push(col(name.as_str()));
9109                }
9110                Expr::NamedArg { name, value } => {
9111                    let expr = translate_expr(value, &ctx).map_err(runtime_err)?;
9112                    select_exprs.push(expr.alias(name));
9113                }
9114                Expr::String(name) => {
9115                    select_exprs.push(col(name.as_str()));
9116                }
9117                _ => {
9118                    let expr = translate_expr(arg, &ctx).map_err(runtime_err)?;
9119                    select_exprs.push(expr);
9120                }
9121            }
9122        }
9123        let selected = df
9124            .select(select_exprs)
9125            .map_err(|e| runtime_err(format!("{e}")))?;
9126        Ok(Value::Table(TlTable { df: selected }))
9127    }
9128
9129    /// `table |> sort(col, "desc")` or `table |> sort(col)` (default asc)
9130    fn table_sort(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9131        if args.is_empty() {
9132            return Err(runtime_err(
9133                "sort() expects at least 1 argument (column)".into(),
9134            ));
9135        }
9136        let mut sort_exprs = Vec::new();
9137        let mut i = 0;
9138        while i < args.len() {
9139            let col_name = match &args[i] {
9140                Expr::Ident(name) => name.clone(),
9141                Expr::String(name) => name.clone(),
9142                _ => {
9143                    return Err(runtime_err(
9144                        "sort() column must be an identifier or string".into(),
9145                    ));
9146                }
9147            };
9148            i += 1;
9149            // Check for optional "asc"/"desc" direction
9150            let ascending = if i < args.len() {
9151                match &args[i] {
9152                    Expr::String(dir) if dir == "desc" || dir == "DESC" => {
9153                        i += 1;
9154                        false
9155                    }
9156                    Expr::String(dir) if dir == "asc" || dir == "ASC" => {
9157                        i += 1;
9158                        true
9159                    }
9160                    _ => true,
9161                }
9162            } else {
9163                true
9164            };
9165            sort_exprs.push(
9166                col(col_name.as_str()).sort(ascending, true), // nulls last
9167            );
9168        }
9169        let sorted = df
9170            .sort(sort_exprs)
9171            .map_err(|e| runtime_err(format!("{e}")))?;
9172        Ok(Value::Table(TlTable { df: sorted }))
9173    }
9174
9175    /// `table |> with { col_name = expr, ... }` — add derived columns
9176    fn table_with(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9177        if args.len() != 1 {
9178            return Err(runtime_err(
9179                "with() expects 1 argument (map of column definitions)".into(),
9180            ));
9181        }
9182        let pairs = match &args[0] {
9183            Expr::Map(pairs) => pairs,
9184            _ => {
9185                return Err(runtime_err(
9186                    "with() expects a map { col = expr, ... }".into(),
9187                ));
9188            }
9189        };
9190        let ctx = self.build_translate_context();
9191        let mut result_df = df;
9192        for (key, value_expr) in pairs {
9193            let col_name = match key {
9194                Expr::String(s) => s.clone(),
9195                Expr::Ident(s) => s.clone(),
9196                _ => {
9197                    return Err(runtime_err(
9198                        "with() key must be a string or identifier".into(),
9199                    ));
9200                }
9201            };
9202            let df_expr = translate_expr(value_expr, &ctx).map_err(runtime_err)?;
9203            result_df = result_df
9204                .with_column(&col_name, df_expr)
9205                .map_err(|e| runtime_err(format!("{e}")))?;
9206        }
9207        Ok(Value::Table(TlTable { df: result_df }))
9208    }
9209
9210    /// `table |> aggregate(by: "col", total: sum(amount), n: count())`
9211    fn table_aggregate(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9212        let ctx = self.build_translate_context();
9213        let mut group_by_cols: Vec<tl_data::datafusion::prelude::Expr> = Vec::new();
9214        let mut agg_exprs: Vec<tl_data::datafusion::prelude::Expr> = Vec::new();
9215
9216        for arg in args {
9217            match arg {
9218                Expr::NamedArg { name, value } if name == "by" => {
9219                    // by: "col" or by: col
9220                    match value.as_ref() {
9221                        Expr::String(col_name) => {
9222                            group_by_cols.push(col(col_name.as_str()));
9223                        }
9224                        Expr::Ident(col_name) => {
9225                            group_by_cols.push(col(col_name.as_str()));
9226                        }
9227                        Expr::List(items) => {
9228                            for item in items {
9229                                match item {
9230                                    Expr::String(s) => group_by_cols.push(col(s.as_str())),
9231                                    Expr::Ident(s) => group_by_cols.push(col(s.as_str())),
9232                                    _ => {
9233                                        return Err(runtime_err(
9234                                            "by: list items must be strings or identifiers".into(),
9235                                        ));
9236                                    }
9237                                }
9238                            }
9239                        }
9240                        _ => return Err(runtime_err("by: must be a column name or list".into())),
9241                    }
9242                }
9243                Expr::NamedArg { name, value } => {
9244                    // Named aggregate: total: sum(amount)
9245                    let agg_expr = translate_expr(value, &ctx).map_err(runtime_err)?;
9246                    agg_exprs.push(agg_expr.alias(name));
9247                }
9248                _ => {
9249                    // Positional aggregate
9250                    let agg_expr = translate_expr(arg, &ctx).map_err(runtime_err)?;
9251                    agg_exprs.push(agg_expr);
9252                }
9253            }
9254        }
9255
9256        let aggregated = df
9257            .aggregate(group_by_cols, agg_exprs)
9258            .map_err(|e| runtime_err(format!("{e}")))?;
9259        Ok(Value::Table(TlTable { df: aggregated }))
9260    }
9261
9262    /// `table |> join(right_table, on: left_col == right_col, kind: "inner")`
9263    fn table_join(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9264        if args.is_empty() {
9265            return Err(runtime_err(
9266                "join() expects at least 1 argument (right table)".into(),
9267            ));
9268        }
9269
9270        // First positional arg: right table (evaluate it)
9271        let right_table = self.eval_expr(&args[0])?;
9272        let right_df = match right_table {
9273            Value::Table(t) => t.df,
9274            _ => return Err(runtime_err("join() first arg must be a table".into())),
9275        };
9276
9277        let mut left_cols: Vec<&str> = Vec::new();
9278        let mut right_cols: Vec<&str> = Vec::new();
9279        let mut join_type = JoinType::Inner;
9280        let mut on_col_names: Vec<(String, String)> = Vec::new();
9281
9282        for arg in &args[1..] {
9283            match arg {
9284                Expr::NamedArg { name, value } if name == "on" => {
9285                    // on: left_col == right_col
9286                    match value.as_ref() {
9287                        Expr::BinOp {
9288                            left,
9289                            op: BinOp::Eq,
9290                            right,
9291                        } => {
9292                            let left_col = match left.as_ref() {
9293                                Expr::Ident(s) => s.clone(),
9294                                Expr::String(s) => s.clone(),
9295                                _ => {
9296                                    return Err(runtime_err(
9297                                        "on: left side must be a column name".into(),
9298                                    ));
9299                                }
9300                            };
9301                            let right_col = match right.as_ref() {
9302                                Expr::Ident(s) => s.clone(),
9303                                Expr::String(s) => s.clone(),
9304                                _ => {
9305                                    return Err(runtime_err(
9306                                        "on: right side must be a column name".into(),
9307                                    ));
9308                                }
9309                            };
9310                            on_col_names.push((left_col, right_col));
9311                        }
9312                        _ => {
9313                            return Err(runtime_err(
9314                                "on: must be an equality expression (col1 == col2)".into(),
9315                            ));
9316                        }
9317                    }
9318                }
9319                Expr::NamedArg { name, value } if name == "kind" => {
9320                    let kind_val = self.eval_expr(value)?;
9321                    let kind_str = match &kind_val {
9322                        Value::String(s) => s.as_str(),
9323                        _ => return Err(runtime_err("kind: must be a string".into())),
9324                    };
9325                    join_type = match kind_str {
9326                        "inner" => JoinType::Inner,
9327                        "left" => JoinType::Left,
9328                        "right" => JoinType::Right,
9329                        "full" => JoinType::Full,
9330                        _ => return Err(runtime_err(format!("Unknown join type: {kind_str}"))),
9331                    };
9332                }
9333                _ => {} // ignore other args
9334            }
9335        }
9336
9337        // Build column references
9338        for (l, r) in &on_col_names {
9339            left_cols.push(l.as_str());
9340            right_cols.push(r.as_str());
9341        }
9342
9343        let joined = df
9344            .join(right_df, join_type, &left_cols, &right_cols, None)
9345            .map_err(|e| runtime_err(format!("{e}")))?;
9346        Ok(Value::Table(TlTable { df: joined }))
9347    }
9348
9349    /// `table |> head(n)` or `table |> limit(n)`
9350    fn table_limit(&mut self, df: DataFrame, args: &[Expr]) -> Result<Value, TlError> {
9351        let n = match args.first() {
9352            Some(expr) => {
9353                let val = self.eval_expr(expr)?;
9354                match val {
9355                    Value::Int(n) => n as usize,
9356                    _ => return Err(runtime_err("head/limit expects an integer".into())),
9357                }
9358            }
9359            None => 10,
9360        };
9361        let limited = df
9362            .limit(0, Some(n))
9363            .map_err(|e| runtime_err(format!("{e}")))?;
9364        Ok(Value::Table(TlTable { df: limited }))
9365    }
9366}
9367
9368/// Convert TL type annotations to Arrow DataTypes.
9369fn tl_type_to_arrow(ty: &TypeExpr) -> ArrowDataType {
9370    match ty {
9371        TypeExpr::Named(name) => match name.as_str() {
9372            "int64" | "int" => ArrowDataType::Int64,
9373            "int32" => ArrowDataType::Int32,
9374            "int16" => ArrowDataType::Int16,
9375            "float64" | "float" => ArrowDataType::Float64,
9376            "float32" => ArrowDataType::Float32,
9377            "string" | "str" | "text" => ArrowDataType::Utf8,
9378            "bool" | "boolean" => ArrowDataType::Boolean,
9379            _ => ArrowDataType::Utf8, // fallback
9380        },
9381        TypeExpr::Optional(inner) => tl_type_to_arrow(inner), // nullable is always true in Arrow
9382        _ => ArrowDataType::Utf8,                             // fallback for complex types
9383    }
9384}
9385
9386/// Resolve a connection name via TL_CONFIG_PATH config file.
9387fn resolve_tl_config_connection_interp(name: &str) -> String {
9388    if name.contains('=') || name.contains("://") {
9389        return name.to_string();
9390    }
9391    let config_path =
9392        std::env::var("TL_CONFIG_PATH").unwrap_or_else(|_| "tl_config.json".to_string());
9393    let Ok(contents) = std::fs::read_to_string(&config_path) else {
9394        return name.to_string();
9395    };
9396    let Ok(json) = serde_json::from_str::<serde_json::Value>(&contents) else {
9397        return name.to_string();
9398    };
9399    if let Some(conn) = json
9400        .get("connections")
9401        .and_then(|c| c.get(name))
9402        .and_then(|v| v.as_str())
9403    {
9404        return conn.to_string();
9405    }
9406    if let Some(conn) = json.get(name).and_then(|v| v.as_str()) {
9407        return conn.to_string();
9408    }
9409    name.to_string()
9410}
9411
9412fn runtime_err(message: String) -> TlError {
9413    TlError::Runtime(RuntimeError {
9414        message,
9415        span: None,
9416        stack_trace: vec![],
9417    })
9418}
9419
9420fn runtime_err_s(message: &str) -> TlError {
9421    TlError::Runtime(RuntimeError {
9422        message: message.to_string(),
9423        span: None,
9424        stack_trace: vec![],
9425    })
9426}
9427
9428/// Resolve a file path within a package directory for package imports.
9429fn resolve_package_file_interp(pkg_root: &std::path::Path, remaining: &[&str]) -> Option<String> {
9430    if remaining.is_empty() {
9431        let src = pkg_root.join("src");
9432        for entry in &["lib.tl", "mod.tl", "main.tl"] {
9433            let p = src.join(entry);
9434            if p.exists() {
9435                return Some(p.to_string_lossy().to_string());
9436            }
9437        }
9438        for entry in &["mod.tl", "lib.tl"] {
9439            let p = pkg_root.join(entry);
9440            if p.exists() {
9441                return Some(p.to_string_lossy().to_string());
9442            }
9443        }
9444        return None;
9445    }
9446
9447    let rel = remaining.join("/");
9448    let src = pkg_root.join("src");
9449
9450    let file_path = src.join(format!("{rel}.tl"));
9451    if file_path.exists() {
9452        return Some(file_path.to_string_lossy().to_string());
9453    }
9454
9455    let dir_path = src.join(&rel).join("mod.tl");
9456    if dir_path.exists() {
9457        return Some(dir_path.to_string_lossy().to_string());
9458    }
9459
9460    let file_path = pkg_root.join(format!("{rel}.tl"));
9461    if file_path.exists() {
9462        return Some(file_path.to_string_lossy().to_string());
9463    }
9464
9465    let dir_path = pkg_root.join(&rel).join("mod.tl");
9466    if dir_path.exists() {
9467        return Some(dir_path.to_string_lossy().to_string());
9468    }
9469
9470    if remaining.len() > 1 {
9471        let parent = &remaining[..remaining.len() - 1];
9472        let parent_rel = parent.join("/");
9473        let parent_file = src.join(format!("{parent_rel}.tl"));
9474        if parent_file.exists() {
9475            return Some(parent_file.to_string_lossy().to_string());
9476        }
9477        let parent_file = pkg_root.join(format!("{parent_rel}.tl"));
9478        if parent_file.exists() {
9479            return Some(parent_file.to_string_lossy().to_string());
9480        }
9481    }
9482
9483    None
9484}
9485
9486/// Convert serde_json::Value to interpreter Value
9487fn json_to_value(v: &serde_json::Value) -> Value {
9488    match v {
9489        serde_json::Value::Null => Value::None,
9490        serde_json::Value::Bool(b) => Value::Bool(*b),
9491        serde_json::Value::Number(n) => {
9492            if let Some(i) = n.as_i64() {
9493                Value::Int(i)
9494            } else {
9495                Value::Float(n.as_f64().unwrap_or(0.0))
9496            }
9497        }
9498        serde_json::Value::String(s) => Value::String(s.clone()),
9499        serde_json::Value::Array(arr) => Value::List(arr.iter().map(json_to_value).collect()),
9500        serde_json::Value::Object(obj) => Value::Map(
9501            obj.iter()
9502                .map(|(k, v)| (k.clone(), json_to_value(v)))
9503                .collect(),
9504        ),
9505    }
9506}
9507
9508/// Convert interpreter Value to serde_json::Value
9509/// Compare two values for equality (used by set operations).
9510fn values_equal(a: &Value, b: &Value) -> bool {
9511    match (a, b) {
9512        (Value::Int(x), Value::Int(y)) => x == y,
9513        (Value::Float(x), Value::Float(y)) => x == y,
9514        (Value::Decimal(x), Value::Decimal(y)) => x == y,
9515        (Value::String(x), Value::String(y)) => x == y,
9516        (Value::Bool(x), Value::Bool(y)) => x == y,
9517        (Value::None, Value::None) => true,
9518        _ => false,
9519    }
9520}
9521
9522fn value_to_json(v: &Value) -> serde_json::Value {
9523    match v {
9524        Value::None => serde_json::Value::Null,
9525        Value::Bool(b) => serde_json::Value::Bool(*b),
9526        Value::Int(n) => serde_json::json!(*n),
9527        Value::Float(n) => serde_json::json!(*n),
9528        Value::String(s) => serde_json::Value::String(s.clone()),
9529        Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
9530        Value::Map(pairs) => {
9531            let obj: serde_json::Map<String, serde_json::Value> = pairs
9532                .iter()
9533                .map(|(k, v)| (k.clone(), value_to_json(v)))
9534                .collect();
9535            serde_json::Value::Object(obj)
9536        }
9537        _ => serde_json::Value::String(format!("{v}")),
9538    }
9539}
9540
9541// ── AI builtin implementations ──────────────────────────
9542
9543impl Interpreter {
9544    fn value_to_f64_list(&self, v: &Value) -> Result<Vec<f64>, TlError> {
9545        match v {
9546            Value::List(items) => {
9547                let mut result = Vec::with_capacity(items.len());
9548                for item in items {
9549                    match item {
9550                        Value::Int(n) => result.push(*n as f64),
9551                        Value::Float(f) => result.push(*f),
9552                        _ => {
9553                            return Err(runtime_err(format!(
9554                                "Expected number in list, got {}",
9555                                item.type_name()
9556                            )));
9557                        }
9558                    }
9559                }
9560                Ok(result)
9561            }
9562            _ => Err(runtime_err(format!("Expected list, got {}", v.type_name()))),
9563        }
9564    }
9565
9566    fn value_to_usize_list(&self, v: &Value) -> Result<Vec<usize>, TlError> {
9567        match v {
9568            Value::List(items) => {
9569                let mut result = Vec::with_capacity(items.len());
9570                for item in items {
9571                    match item {
9572                        Value::Int(n) => result.push(*n as usize),
9573                        _ => {
9574                            return Err(runtime_err(format!(
9575                                "Expected int in shape, got {}",
9576                                item.type_name()
9577                            )));
9578                        }
9579                    }
9580                }
9581                Ok(result)
9582            }
9583            _ => Err(runtime_err(format!(
9584                "Expected list for shape, got {}",
9585                v.type_name()
9586            ))),
9587        }
9588    }
9589
9590    fn builtin_tensor(&mut self, args: &[Value]) -> Result<Value, TlError> {
9591        match args.first() {
9592            Some(Value::List(_)) => {
9593                let data = self.value_to_f64_list(&args[0])?;
9594                if args.len() > 1 {
9595                    let shape = self.value_to_usize_list(&args[1])?;
9596                    let t = tl_ai::TlTensor::from_vec(data, &shape).map_err(runtime_err)?;
9597                    Ok(Value::Tensor(t))
9598                } else {
9599                    Ok(Value::Tensor(tl_ai::TlTensor::from_list(data)))
9600                }
9601            }
9602            _ => Err(runtime_err(
9603                "tensor() expects a list of numbers".to_string(),
9604            )),
9605        }
9606    }
9607
9608    fn builtin_tensor_zeros(&mut self, args: &[Value]) -> Result<Value, TlError> {
9609        let shape = self.value_to_usize_list(
9610            args.first()
9611                .ok_or_else(|| runtime_err("tensor_zeros() expects a shape".to_string()))?,
9612        )?;
9613        Ok(Value::Tensor(tl_ai::TlTensor::zeros(&shape)))
9614    }
9615
9616    fn builtin_tensor_ones(&mut self, args: &[Value]) -> Result<Value, TlError> {
9617        let shape = self.value_to_usize_list(
9618            args.first()
9619                .ok_or_else(|| runtime_err("tensor_ones() expects a shape".to_string()))?,
9620        )?;
9621        Ok(Value::Tensor(tl_ai::TlTensor::ones(&shape)))
9622    }
9623
9624    fn builtin_tensor_shape(&mut self, args: &[Value]) -> Result<Value, TlError> {
9625        match args.first() {
9626            Some(Value::Tensor(t)) => {
9627                let shape = t.shape();
9628                Ok(Value::List(
9629                    shape.into_iter().map(|s| Value::Int(s as i64)).collect(),
9630                ))
9631            }
9632            _ => Err(runtime_err("tensor_shape() expects a tensor".to_string())),
9633        }
9634    }
9635
9636    fn builtin_tensor_reshape(&mut self, args: &[Value]) -> Result<Value, TlError> {
9637        if args.len() != 2 {
9638            return Err(runtime_err(
9639                "tensor_reshape() expects (tensor, shape)".to_string(),
9640            ));
9641        }
9642        match &args[0] {
9643            Value::Tensor(t) => {
9644                let shape = self.value_to_usize_list(&args[1])?;
9645                let reshaped = t.reshape(&shape).map_err(runtime_err)?;
9646                Ok(Value::Tensor(reshaped))
9647            }
9648            _ => Err(runtime_err(
9649                "tensor_reshape() expects a tensor as first argument".to_string(),
9650            )),
9651        }
9652    }
9653
9654    fn builtin_tensor_transpose(&mut self, args: &[Value]) -> Result<Value, TlError> {
9655        match args.first() {
9656            Some(Value::Tensor(t)) => {
9657                let transposed = t.transpose().map_err(runtime_err)?;
9658                Ok(Value::Tensor(transposed))
9659            }
9660            _ => Err(runtime_err(
9661                "tensor_transpose() expects a tensor".to_string(),
9662            )),
9663        }
9664    }
9665
9666    fn builtin_tensor_sum(&mut self, args: &[Value]) -> Result<Value, TlError> {
9667        match args.first() {
9668            Some(Value::Tensor(t)) => Ok(Value::Float(t.sum())),
9669            _ => Err(runtime_err("tensor_sum() expects a tensor".to_string())),
9670        }
9671    }
9672
9673    fn builtin_tensor_mean(&mut self, args: &[Value]) -> Result<Value, TlError> {
9674        match args.first() {
9675            Some(Value::Tensor(t)) => Ok(Value::Float(t.mean())),
9676            _ => Err(runtime_err("tensor_mean() expects a tensor".to_string())),
9677        }
9678    }
9679
9680    fn builtin_tensor_dot(&mut self, args: &[Value]) -> Result<Value, TlError> {
9681        if args.len() != 2 {
9682            return Err(runtime_err("tensor_dot() expects 2 tensors".to_string()));
9683        }
9684        match (&args[0], &args[1]) {
9685            (Value::Tensor(a), Value::Tensor(b)) => {
9686                let result = a.dot(b).map_err(runtime_err)?;
9687                Ok(Value::Tensor(result))
9688            }
9689            _ => Err(runtime_err("tensor_dot() expects two tensors".to_string())),
9690        }
9691    }
9692
9693    fn builtin_predict(&mut self, args: &[Value]) -> Result<Value, TlError> {
9694        if args.len() != 2 {
9695            return Err(runtime_err("predict() expects (model, input)".to_string()));
9696        }
9697        match (&args[0], &args[1]) {
9698            (Value::Model(m), Value::Tensor(t)) => {
9699                let result = tl_ai::predict(m, t).map_err(runtime_err)?;
9700                Ok(Value::Tensor(result))
9701            }
9702            _ => Err(runtime_err("predict() expects (model, tensor)".to_string())),
9703        }
9704    }
9705
9706    fn builtin_similarity(&mut self, args: &[Value]) -> Result<Value, TlError> {
9707        if args.len() != 2 {
9708            return Err(runtime_err("similarity() expects 2 tensors".to_string()));
9709        }
9710        match (&args[0], &args[1]) {
9711            (Value::Tensor(a), Value::Tensor(b)) => {
9712                let sim = tl_ai::similarity(a, b).map_err(runtime_err)?;
9713                Ok(Value::Float(sim))
9714            }
9715            _ => Err(runtime_err("similarity() expects two tensors".to_string())),
9716        }
9717    }
9718
9719    fn builtin_ai_complete(&mut self, args: &[Value]) -> Result<Value, TlError> {
9720        let prompt = match args.first() {
9721            Some(Value::String(s)) => s.clone(),
9722            _ => {
9723                return Err(runtime_err(
9724                    "ai_complete() expects a string prompt".to_string(),
9725                ));
9726            }
9727        };
9728        let model = args.get(1).and_then(|v| match v {
9729            Value::String(s) => Some(s.as_str()),
9730            _ => None,
9731        });
9732        let result = tl_ai::ai_complete(&prompt, model, None, None).map_err(runtime_err)?;
9733        Ok(Value::String(result))
9734    }
9735
9736    fn builtin_ai_chat(&mut self, args: &[Value]) -> Result<Value, TlError> {
9737        let model = match args.first() {
9738            Some(Value::String(s)) => s.clone(),
9739            _ => {
9740                return Err(runtime_err(
9741                    "ai_chat() expects (model, system?, messages)".to_string(),
9742                ));
9743            }
9744        };
9745        let system = args.get(1).and_then(|v| match v {
9746            Value::String(s) => Some(s.as_str()),
9747            _ => None,
9748        });
9749        // Messages as list of [role, content] pairs
9750        let messages = match args.last() {
9751            Some(Value::List(msgs)) => {
9752                let mut result = Vec::new();
9753                for msg in msgs {
9754                    if let Value::List(pair) = msg
9755                        && pair.len() == 2
9756                        && let (Value::String(role), Value::String(content)) = (&pair[0], &pair[1])
9757                    {
9758                        result.push((role.clone(), content.clone()));
9759                    }
9760                }
9761                result
9762            }
9763            _ => Vec::new(),
9764        };
9765        let result = tl_ai::ai_chat(&model, system, &messages).map_err(runtime_err)?;
9766        Ok(Value::String(result))
9767    }
9768
9769    fn builtin_model_save(&mut self, args: &[Value]) -> Result<Value, TlError> {
9770        if args.len() != 2 {
9771            return Err(runtime_err(
9772                "model_save() expects (model, path)".to_string(),
9773            ));
9774        }
9775        match (&args[0], &args[1]) {
9776            (Value::Model(m), Value::String(path)) => {
9777                m.save(std::path::Path::new(path)).map_err(runtime_err)?;
9778                Ok(Value::None)
9779            }
9780            _ => Err(runtime_err(
9781                "model_save() expects (model, string_path)".to_string(),
9782            )),
9783        }
9784    }
9785
9786    fn builtin_model_load(&mut self, args: &[Value]) -> Result<Value, TlError> {
9787        match args.first() {
9788            Some(Value::String(path)) => {
9789                let model =
9790                    tl_ai::TlModel::load(std::path::Path::new(path)).map_err(runtime_err)?;
9791                Ok(Value::Model(model))
9792            }
9793            _ => Err(runtime_err(
9794                "model_load() expects a path string".to_string(),
9795            )),
9796        }
9797    }
9798
9799    fn builtin_model_register(&mut self, args: &[Value]) -> Result<Value, TlError> {
9800        if args.len() != 2 {
9801            return Err(runtime_err(
9802                "model_register() expects (name, model)".to_string(),
9803            ));
9804        }
9805        match (&args[0], &args[1]) {
9806            (Value::String(name), Value::Model(m)) => {
9807                let registry = tl_ai::ModelRegistry::default_location();
9808                registry.register(name, m).map_err(runtime_err)?;
9809                Ok(Value::None)
9810            }
9811            _ => Err(runtime_err(
9812                "model_register() expects (string, model)".to_string(),
9813            )),
9814        }
9815    }
9816
9817    fn builtin_model_list(&mut self, _args: &[Value]) -> Result<Value, TlError> {
9818        let registry = tl_ai::ModelRegistry::default_location();
9819        let names = registry.list();
9820        Ok(Value::List(names.into_iter().map(Value::String).collect()))
9821    }
9822
9823    fn builtin_model_get(&mut self, args: &[Value]) -> Result<Value, TlError> {
9824        match args.first() {
9825            Some(Value::String(name)) => {
9826                let registry = tl_ai::ModelRegistry::default_location();
9827                let model = registry.get(name).map_err(runtime_err)?;
9828                Ok(Value::Model(model))
9829            }
9830            _ => Err(runtime_err(
9831                "model_get() expects a model name string".to_string(),
9832            )),
9833        }
9834    }
9835
9836    /// Execute a statement inside a generator thread, handling yield via channels.
9837    fn exec_stmt_gen(
9838        &mut self,
9839        stmt: &Stmt,
9840        yield_tx: &mpsc::Sender<Result<Value, String>>,
9841        resume_rx: &mpsc::Receiver<()>,
9842    ) -> Result<GenSignal, TlError> {
9843        match &stmt.kind {
9844            StmtKind::Expr(expr) => {
9845                let val = self.eval_expr_gen(expr, yield_tx, resume_rx)?;
9846                self.last_expr_value = Some(val);
9847                Ok(GenSignal::None)
9848            }
9849            StmtKind::Let { name, value, .. } => {
9850                let val = self.eval_expr_gen(value, yield_tx, resume_rx)?;
9851                self.env.set(name.clone(), val);
9852                Ok(GenSignal::None)
9853            }
9854            StmtKind::Return(expr) => {
9855                let val = match expr {
9856                    Some(e) => self.eval_expr_gen(e, yield_tx, resume_rx)?,
9857                    None => Value::None,
9858                };
9859                Ok(GenSignal::Return(val))
9860            }
9861            StmtKind::If {
9862                condition,
9863                then_body,
9864                else_ifs,
9865                else_body,
9866            } => {
9867                let cond = self.eval_expr_gen(condition, yield_tx, resume_rx)?;
9868                if cond.is_truthy() {
9869                    for s in then_body {
9870                        let sig = self.exec_stmt_gen(s, yield_tx, resume_rx)?;
9871                        if !matches!(sig, GenSignal::None) {
9872                            return Ok(sig);
9873                        }
9874                    }
9875                } else {
9876                    let mut handled = false;
9877                    for (ec, eb) in else_ifs {
9878                        let ecv = self.eval_expr_gen(ec, yield_tx, resume_rx)?;
9879                        if ecv.is_truthy() {
9880                            for s in eb {
9881                                let sig = self.exec_stmt_gen(s, yield_tx, resume_rx)?;
9882                                if !matches!(sig, GenSignal::None) {
9883                                    return Ok(sig);
9884                                }
9885                            }
9886                            handled = true;
9887                            break;
9888                        }
9889                    }
9890                    if !handled && let Some(eb) = else_body {
9891                        for s in eb {
9892                            let sig = self.exec_stmt_gen(s, yield_tx, resume_rx)?;
9893                            if !matches!(sig, GenSignal::None) {
9894                                return Ok(sig);
9895                            }
9896                        }
9897                    }
9898                }
9899                Ok(GenSignal::None)
9900            }
9901            StmtKind::While { condition, body } => {
9902                loop {
9903                    let cond = self.eval_expr_gen(condition, yield_tx, resume_rx)?;
9904                    if !cond.is_truthy() {
9905                        break;
9906                    }
9907                    self.env.push_scope();
9908                    let mut brk = false;
9909                    for s in body {
9910                        let sig = self.exec_stmt_gen(s, yield_tx, resume_rx)?;
9911                        match sig {
9912                            GenSignal::Break => {
9913                                brk = true;
9914                                break;
9915                            }
9916                            GenSignal::Continue => break,
9917                            GenSignal::Return(v) => {
9918                                self.env.pop_scope();
9919                                return Ok(GenSignal::Return(v));
9920                            }
9921                            GenSignal::Throw(v) => {
9922                                self.env.pop_scope();
9923                                return Ok(GenSignal::Throw(v));
9924                            }
9925                            _ => {}
9926                        }
9927                    }
9928                    self.env.pop_scope();
9929                    if brk {
9930                        break;
9931                    }
9932                }
9933                Ok(GenSignal::None)
9934            }
9935            StmtKind::For { name, iter, body } | StmtKind::ParallelFor { name, iter, body } => {
9936                let iter_val = self.eval_expr_gen(iter, yield_tx, resume_rx)?;
9937                let items = match iter_val {
9938                    Value::List(items) => items,
9939                    Value::Map(pairs) => pairs
9940                        .into_iter()
9941                        .map(|(k, v)| Value::List(vec![Value::String(k), v]))
9942                        .collect(),
9943                    Value::Generator(g) => {
9944                        // For generator: pull items one by one
9945                        let mut results = Vec::new();
9946                        loop {
9947                            let val = self.interpreter_next(&g)?;
9948                            if matches!(val, Value::None) {
9949                                break;
9950                            }
9951                            results.push(val);
9952                        }
9953                        results
9954                    }
9955                    _ => {
9956                        return Err(runtime_err(format!(
9957                            "Cannot iterate over {}",
9958                            iter_val.type_name()
9959                        )));
9960                    }
9961                };
9962                for item in items {
9963                    self.env.push_scope();
9964                    self.env.set(name.clone(), item);
9965                    let mut brk = false;
9966                    for s in body {
9967                        let sig = self.exec_stmt_gen(s, yield_tx, resume_rx)?;
9968                        match sig {
9969                            GenSignal::Break => {
9970                                brk = true;
9971                                break;
9972                            }
9973                            GenSignal::Continue => break,
9974                            GenSignal::Return(v) => {
9975                                self.env.pop_scope();
9976                                return Ok(GenSignal::Return(v));
9977                            }
9978                            GenSignal::Throw(v) => {
9979                                self.env.pop_scope();
9980                                return Ok(GenSignal::Throw(v));
9981                            }
9982                            _ => {}
9983                        }
9984                    }
9985                    self.env.pop_scope();
9986                    if brk {
9987                        break;
9988                    }
9989                }
9990                Ok(GenSignal::None)
9991            }
9992            StmtKind::Break => Ok(GenSignal::Break),
9993            StmtKind::Continue => Ok(GenSignal::Continue),
9994            StmtKind::Throw(expr) => {
9995                let val = self.eval_expr_gen(expr, yield_tx, resume_rx)?;
9996                Ok(GenSignal::Throw(val))
9997            }
9998            StmtKind::FnDecl {
9999                name,
10000                params,
10001                body,
10002                is_generator,
10003                ..
10004            } => {
10005                let func = Value::Function {
10006                    name: name.clone(),
10007                    params: params.clone(),
10008                    body: body.clone(),
10009                    is_generator: *is_generator,
10010                };
10011                self.env.set(name.clone(), func);
10012                Ok(GenSignal::None)
10013            }
10014            // For other statements, delegate to regular exec_stmt
10015            _ => {
10016                let sig = self.exec_stmt(stmt)?;
10017                Ok(match sig {
10018                    Signal::None => GenSignal::None,
10019                    Signal::Return(v) => GenSignal::Return(v),
10020                    Signal::Break => GenSignal::Break,
10021                    Signal::Continue => GenSignal::Continue,
10022                    Signal::Throw(v) => GenSignal::Throw(v),
10023                    Signal::Yield(v) => GenSignal::Yield(v),
10024                })
10025            }
10026        }
10027    }
10028
10029    /// Evaluate expression inside a generator, handling yield.
10030    fn eval_expr_gen(
10031        &mut self,
10032        expr: &Expr,
10033        yield_tx: &mpsc::Sender<Result<Value, String>>,
10034        resume_rx: &mpsc::Receiver<()>,
10035    ) -> Result<Value, TlError> {
10036        match expr {
10037            Expr::Yield(opt_expr) => {
10038                let val = match opt_expr {
10039                    Some(e) => self.eval_expr_gen(e, yield_tx, resume_rx)?,
10040                    None => Value::None,
10041                };
10042                // Send the yielded value to the consumer
10043                yield_tx
10044                    .send(Ok(val.clone()))
10045                    .map_err(|_| runtime_err("Generator consumer disconnected".to_string()))?;
10046                // Wait for resume signal
10047                resume_rx
10048                    .recv()
10049                    .map_err(|_| runtime_err("Generator consumer disconnected".to_string()))?;
10050                Ok(val)
10051            }
10052            // For assign with yield: handle specially
10053            Expr::Assign { target, value } => {
10054                let val = self.eval_expr_gen(value, yield_tx, resume_rx)?;
10055                match target.as_ref() {
10056                    Expr::Ident(name) => {
10057                        if !self.env.update(name, val.clone()) {
10058                            return Err(runtime_err(format!("Variable '{name}' not found")));
10059                        }
10060                        Ok(val)
10061                    }
10062                    _ => {
10063                        // For complex assignments, use regular eval
10064                        self.eval_expr(&Expr::Assign {
10065                            target: target.clone(),
10066                            value: Box::new(Expr::None),
10067                        })
10068                        .ok();
10069                        // Actually just set the value
10070                        Ok(val)
10071                    }
10072                }
10073            }
10074            // For most expressions, delegate to regular eval_expr
10075            _ => self.eval_expr(expr),
10076        }
10077    }
10078
10079    fn exec_train(
10080        &mut self,
10081        name: &str,
10082        algorithm: &str,
10083        config: &[(String, Expr)],
10084    ) -> Result<Signal, TlError> {
10085        // Extract config values
10086        let mut features_val = None;
10087        let mut target_val = None;
10088        let mut feature_names = Vec::new();
10089        let mut target_name = String::new();
10090
10091        for (key, expr) in config {
10092            let val = self.eval_expr(expr)?;
10093            match key.as_str() {
10094                "data" => features_val = Some(val),
10095                "target" => {
10096                    if let Value::String(s) = &val {
10097                        target_name = s.clone();
10098                    }
10099                    target_val = Some(val);
10100                }
10101                "features" => {
10102                    if let Value::List(items) = &val {
10103                        for item in items {
10104                            if let Value::String(s) = item {
10105                                feature_names.push(s.clone());
10106                            }
10107                        }
10108                    }
10109                }
10110                _ => {} // ignore unknown config keys for now
10111            }
10112        }
10113
10114        // When data is a Table, extract features and target from it
10115        if let Some(Value::Table(ref tbl)) = features_val {
10116            let engine = tl_data::DataEngine::new();
10117            let batches = engine.collect(tbl.df.clone()).map_err(runtime_err)?;
10118            if batches.is_empty() {
10119                return Err(runtime_err("train: empty dataset".to_string()));
10120            }
10121            let batch = &batches[0];
10122            let schema = batch.schema();
10123
10124            // Determine feature columns
10125            if feature_names.is_empty() {
10126                for field in schema.fields() {
10127                    if field.name() != &target_name {
10128                        feature_names.push(field.name().clone());
10129                    }
10130                }
10131            }
10132
10133            let n_rows = batch.num_rows();
10134            let n_features = feature_names.len();
10135
10136            // Extract feature columns
10137            let mut col_data: Vec<Vec<f64>> = Vec::new();
10138            for col_name in &feature_names {
10139                let col_idx = schema
10140                    .index_of(col_name)
10141                    .map_err(|_| runtime_err(format!("Column not found: {col_name}")))?;
10142                let arr = batch.column(col_idx);
10143                let mut vals = Vec::with_capacity(n_rows);
10144                Self::extract_f64_col(arr, &mut vals)?;
10145                col_data.push(vals);
10146            }
10147
10148            // Convert to row-major
10149            let mut row_major = Vec::with_capacity(n_rows * n_features);
10150            for row in 0..n_rows {
10151                for col in &col_data {
10152                    row_major.push(col[row]);
10153                }
10154            }
10155            let features_tensor =
10156                tl_ai::TlTensor::from_vec(row_major, &[n_rows, n_features]).map_err(runtime_err)?;
10157
10158            // Extract target column
10159            let target_idx = schema
10160                .index_of(&target_name)
10161                .map_err(|_| runtime_err(format!("Target column not found: {target_name}")))?;
10162            let target_arr = batch.column(target_idx);
10163            let mut target_data = Vec::with_capacity(n_rows);
10164            Self::extract_f64_col(target_arr, &mut target_data)?;
10165            let target_tensor = tl_ai::TlTensor::from_list(target_data);
10166
10167            let train_config = tl_ai::TrainConfig {
10168                features: features_tensor,
10169                target: target_tensor,
10170                feature_names,
10171                target_name,
10172                model_name: name.to_string(),
10173                split_ratio: 1.0,
10174                hyperparams: std::collections::HashMap::new(),
10175            };
10176
10177            let model = tl_ai::train(algorithm, &train_config).map_err(runtime_err)?;
10178
10179            if let Some(meta) = model.metadata() {
10180                let metrics_str: Vec<String> = meta
10181                    .metrics
10182                    .iter()
10183                    .map(|(k, v)| format!("{k}={v:.4}"))
10184                    .collect();
10185                if !metrics_str.is_empty() {
10186                    let msg = format!(
10187                        "Trained model '{}' ({algorithm}): {}",
10188                        name,
10189                        metrics_str.join(", ")
10190                    );
10191                    println!("{msg}");
10192                    self.output.push(msg);
10193                }
10194            }
10195
10196            self.env.set(name.to_string(), Value::Model(model));
10197            return Ok(Signal::None);
10198        }
10199
10200        // Convert data to tensors (non-table path)
10201        let features_tensor = match features_val {
10202            Some(Value::Tensor(t)) => t,
10203            Some(Value::List(items)) => {
10204                // Treat as 2D list of lists or flat list
10205                let mut all_data = Vec::new();
10206                let mut n_cols = 0;
10207                for item in &items {
10208                    match item {
10209                        Value::List(row) => {
10210                            if n_cols == 0 {
10211                                n_cols = row.len();
10212                            }
10213                            for v in row {
10214                                match v {
10215                                    Value::Int(n) => all_data.push(*n as f64),
10216                                    Value::Float(f) => all_data.push(*f),
10217                                    _ => {
10218                                        return Err(runtime_err(
10219                                            "Training data must be numeric".to_string(),
10220                                        ));
10221                                    }
10222                                }
10223                            }
10224                        }
10225                        Value::Int(n) => all_data.push(*n as f64),
10226                        Value::Float(f) => all_data.push(*f),
10227                        _ => return Err(runtime_err("Training data must be numeric".to_string())),
10228                    }
10229                }
10230                if n_cols == 0 {
10231                    n_cols = 1;
10232                }
10233                let n_rows = all_data.len() / n_cols;
10234                tl_ai::TlTensor::from_vec(all_data, &[n_rows, n_cols]).map_err(runtime_err)?
10235            }
10236            _ => return Err(runtime_err("train requires 'data' config key".to_string())),
10237        };
10238
10239        let target_tensor = match target_val {
10240            Some(Value::Tensor(t)) => t,
10241            Some(Value::List(items)) => {
10242                let data: Result<Vec<f64>, _> = items
10243                    .iter()
10244                    .map(|v| match v {
10245                        Value::Int(n) => Ok(*n as f64),
10246                        Value::Float(f) => Ok(*f),
10247                        _ => Err(runtime_err("Target values must be numeric".to_string())),
10248                    })
10249                    .collect();
10250                tl_ai::TlTensor::from_list(data?)
10251            }
10252            Some(Value::String(_)) => {
10253                return Err(runtime_err(
10254                    "String target column requires table data. Pass data as a table.".to_string(),
10255                ));
10256            }
10257            _ => {
10258                return Err(runtime_err(
10259                    "train requires 'target' config key with numeric data".to_string(),
10260                ));
10261            }
10262        };
10263
10264        if feature_names.is_empty() {
10265            let n_features = features_tensor.shape().get(1).copied().unwrap_or(1);
10266            feature_names = (0..n_features).map(|i| format!("x{i}")).collect();
10267        }
10268
10269        let train_config = tl_ai::TrainConfig {
10270            features: features_tensor,
10271            target: target_tensor,
10272            feature_names,
10273            target_name,
10274            model_name: name.to_string(),
10275            split_ratio: 1.0,
10276            hyperparams: std::collections::HashMap::new(),
10277        };
10278
10279        let model = tl_ai::train(algorithm, &train_config).map_err(runtime_err)?;
10280
10281        // Print training metrics
10282        if let Some(meta) = model.metadata() {
10283            let metrics_str: Vec<String> = meta
10284                .metrics
10285                .iter()
10286                .map(|(k, v)| format!("{k}={v:.4}"))
10287                .collect();
10288            if !metrics_str.is_empty() {
10289                let msg = format!(
10290                    "Trained model '{}' ({algorithm}): {}",
10291                    name,
10292                    metrics_str.join(", ")
10293                );
10294                println!("{msg}");
10295                self.output.push(msg);
10296            }
10297        }
10298
10299        self.env.set(name.to_string(), Value::Model(model));
10300        Ok(Signal::None)
10301    }
10302
10303    fn extract_f64_col(
10304        col: &Arc<dyn tl_data::datafusion::arrow::array::Array>,
10305        out: &mut Vec<f64>,
10306    ) -> Result<(), TlError> {
10307        use tl_data::datafusion::arrow::array::{
10308            Array, Float32Array, Float64Array, Int32Array, Int64Array,
10309        };
10310        let len = col.len();
10311        if let Some(arr) = col.as_any().downcast_ref::<Float64Array>() {
10312            for i in 0..len {
10313                out.push(if arr.is_null(i) { 0.0 } else { arr.value(i) });
10314            }
10315        } else if let Some(arr) = col.as_any().downcast_ref::<Int64Array>() {
10316            for i in 0..len {
10317                out.push(if arr.is_null(i) {
10318                    0.0
10319                } else {
10320                    arr.value(i) as f64
10321                });
10322            }
10323        } else if let Some(arr) = col.as_any().downcast_ref::<Float32Array>() {
10324            for i in 0..len {
10325                out.push(if arr.is_null(i) {
10326                    0.0
10327                } else {
10328                    arr.value(i) as f64
10329                });
10330            }
10331        } else if let Some(arr) = col.as_any().downcast_ref::<Int32Array>() {
10332            for i in 0..len {
10333                out.push(if arr.is_null(i) {
10334                    0.0
10335                } else {
10336                    arr.value(i) as f64
10337                });
10338            }
10339        } else {
10340            return Err(runtime_err(
10341                "Column must be numeric (int32, int64, float32, float64)".to_string(),
10342            ));
10343        }
10344        Ok(())
10345    }
10346}
10347
10348// ── Streaming & Pipeline execution ──────────────────────────
10349
10350impl Interpreter {
10351    #[allow(clippy::too_many_arguments)]
10352    fn exec_pipeline(
10353        &mut self,
10354        name: &str,
10355        extract: &[Stmt],
10356        transform: &[Stmt],
10357        load: &[Stmt],
10358        schedule: &Option<String>,
10359        timeout: &Option<String>,
10360        retries: &Option<i64>,
10361        on_failure: &Option<Vec<Stmt>>,
10362        on_success: &Option<Vec<Stmt>>,
10363    ) -> Result<Signal, TlError> {
10364        let timeout_ms = timeout
10365            .as_ref()
10366            .and_then(|t| tl_stream::parse_duration(t).ok());
10367
10368        let def = PipelineDef {
10369            name: name.to_string(),
10370            schedule: schedule.clone(),
10371            timeout_ms,
10372            retries: retries.unwrap_or(0) as u32,
10373        };
10374
10375        let runner = PipelineRunner::new(def.clone());
10376
10377        // Clone what we need for the closure
10378        let extract = extract.to_vec();
10379        let transform = transform.to_vec();
10380        let load = load.to_vec();
10381
10382        // Run the pipeline blocks with shared scope and retry logic
10383        let max_attempts = def.retries + 1;
10384        let mut last_error = String::new();
10385        let mut succeeded = false;
10386
10387        for _attempt in 0..max_attempts {
10388            // Push a shared scope for all pipeline blocks
10389            self.env.push_scope();
10390            let mut attempt_ok = true;
10391
10392            // Execute extract block
10393            for stmt in &extract {
10394                match self.exec_stmt(stmt) {
10395                    Ok(Signal::Return(v)) => {
10396                        self.env.pop_scope();
10397                        return Ok(Signal::Return(v));
10398                    }
10399                    Err(e) => {
10400                        last_error = format!("{e}");
10401                        attempt_ok = false;
10402                        break;
10403                    }
10404                    _ => {}
10405                }
10406            }
10407
10408            // Execute transform block
10409            if attempt_ok {
10410                for stmt in &transform {
10411                    match self.exec_stmt(stmt) {
10412                        Ok(Signal::Return(v)) => {
10413                            self.env.pop_scope();
10414                            return Ok(Signal::Return(v));
10415                        }
10416                        Err(e) => {
10417                            last_error = format!("{e}");
10418                            attempt_ok = false;
10419                            break;
10420                        }
10421                        _ => {}
10422                    }
10423                }
10424            }
10425
10426            // Execute load block
10427            if attempt_ok {
10428                for stmt in &load {
10429                    match self.exec_stmt(stmt) {
10430                        Ok(Signal::Return(v)) => {
10431                            self.env.pop_scope();
10432                            return Ok(Signal::Return(v));
10433                        }
10434                        Err(e) => {
10435                            last_error = format!("{e}");
10436                            attempt_ok = false;
10437                            break;
10438                        }
10439                        _ => {}
10440                    }
10441                }
10442            }
10443
10444            self.env.pop_scope();
10445
10446            if attempt_ok {
10447                succeeded = true;
10448                break;
10449            }
10450        }
10451
10452        if succeeded {
10453            if let Some(success_block) = on_success {
10454                self.exec_block(success_block)?;
10455            }
10456            // Store pipeline result
10457            let _result = tl_stream::PipelineResult {
10458                name: name.to_string(),
10459                status: PipelineStatus::Success,
10460                started_at: String::new(),
10461                ended_at: String::new(),
10462                rows_processed: 0,
10463                attempts: 1,
10464            };
10465            self.output.push(format!("Pipeline '{}': success", name));
10466            let _ = runner; // use the runner to suppress warnings
10467        } else {
10468            if let Some(failure_block) = on_failure {
10469                self.exec_block(failure_block)?;
10470            }
10471            self.output
10472                .push(format!("Pipeline '{}': failed — {}", name, last_error));
10473        }
10474
10475        // Store pipeline def in env
10476        self.env.set(name.to_string(), Value::Pipeline(def));
10477        Ok(Signal::None)
10478    }
10479
10480    fn exec_stream_decl(
10481        &mut self,
10482        name: &str,
10483        source: &Expr,
10484        _transform: &[Stmt],
10485        sink: &Option<Expr>,
10486        window: &Option<tl_ast::WindowSpec>,
10487        watermark: &Option<String>,
10488    ) -> Result<Signal, TlError> {
10489        let _source_val = self.eval_expr(source)?;
10490
10491        let window_type = window.as_ref().map(|w| match w {
10492            tl_ast::WindowSpec::Tumbling(dur) => {
10493                let ms = tl_stream::parse_duration(dur).unwrap_or(0);
10494                tl_stream::window::WindowType::Tumbling { duration_ms: ms }
10495            }
10496            tl_ast::WindowSpec::Sliding(win, slide) => {
10497                let wms = tl_stream::parse_duration(win).unwrap_or(0);
10498                let sms = tl_stream::parse_duration(slide).unwrap_or(0);
10499                tl_stream::window::WindowType::Sliding {
10500                    window_ms: wms,
10501                    slide_ms: sms,
10502                }
10503            }
10504            tl_ast::WindowSpec::Session(gap) => {
10505                let ms = tl_stream::parse_duration(gap).unwrap_or(0);
10506                tl_stream::window::WindowType::Session { gap_ms: ms }
10507            }
10508        });
10509
10510        let watermark_ms = watermark
10511            .as_ref()
10512            .and_then(|w| tl_stream::parse_duration(w).ok());
10513
10514        let def = StreamDef {
10515            name: name.to_string(),
10516            window: window_type,
10517            watermark_ms,
10518        };
10519
10520        // Evaluate sink if provided
10521        if let Some(_sink_expr) = sink {
10522            // sink is evaluated but not used until stream is started
10523        }
10524
10525        // Store stream definition
10526        self.env.set(name.to_string(), Value::Stream(def));
10527        self.output.push(format!("Stream '{}' declared", name));
10528        Ok(Signal::None)
10529    }
10530
10531    // ── Phase 34: Agent Framework ──────────────────────────────
10532
10533    #[allow(clippy::too_many_arguments)]
10534    fn exec_agent(
10535        &mut self,
10536        name: &str,
10537        model: &str,
10538        system_prompt: &Option<String>,
10539        tools: &[(String, Expr)],
10540        max_turns: &Option<i64>,
10541        temperature: &Option<f64>,
10542        max_tokens: &Option<i64>,
10543        base_url: &Option<String>,
10544        api_key: &Option<String>,
10545        output_format: &Option<String>,
10546        on_tool_call: &Option<Vec<Stmt>>,
10547        on_complete: &Option<Vec<Stmt>>,
10548        mcp_servers: &[Expr],
10549    ) -> Result<Signal, TlError> {
10550        let mut agent_tools = Vec::new();
10551        for (tool_name, tool_expr) in tools {
10552            let val = self.eval_expr(tool_expr)?;
10553            let (desc, params) = self.extract_agent_tool(&val);
10554            agent_tools.push(tl_stream::AgentTool {
10555                name: tool_name.clone(),
10556                description: desc,
10557                parameters: params,
10558            });
10559        }
10560
10561        // Resolve MCP server clients from expressions
10562        #[cfg(feature = "mcp")]
10563        let mcp_clients: Vec<Arc<tl_mcp::McpClient>> = {
10564            let mut clients = Vec::new();
10565            for expr in mcp_servers {
10566                let val = self.eval_expr(expr)?;
10567                if let Value::McpClient(client) = val {
10568                    clients.push(client);
10569                }
10570            }
10571            clients
10572        };
10573        #[cfg(not(feature = "mcp"))]
10574        let _ = mcp_servers;
10575
10576        let def = tl_stream::AgentDef {
10577            name: name.to_string(),
10578            model: model.to_string(),
10579            system_prompt: system_prompt.clone(),
10580            tools: agent_tools,
10581            max_turns: max_turns.unwrap_or(10) as u32,
10582            temperature: *temperature,
10583            max_tokens: max_tokens.map(|n| n as u32),
10584            base_url: base_url.clone(),
10585            api_key: api_key.clone(),
10586            output_format: output_format.clone(),
10587        };
10588
10589        // Store MCP clients for this agent
10590        #[cfg(feature = "mcp")]
10591        if !mcp_clients.is_empty() {
10592            self.mcp_agent_clients.insert(name.to_string(), mcp_clients);
10593        }
10594
10595        self.env.set(name.to_string(), Value::Agent(def));
10596
10597        // Store lifecycle hooks as functions
10598        if let Some(stmts) = on_tool_call {
10599            let hook = Value::Function {
10600                name: format!("__agent_{name}_on_tool_call__"),
10601                params: vec![
10602                    Param {
10603                        name: "tool_name".into(),
10604                        type_ann: None,
10605                    },
10606                    Param {
10607                        name: "tool_args".into(),
10608                        type_ann: None,
10609                    },
10610                    Param {
10611                        name: "tool_result".into(),
10612                        type_ann: None,
10613                    },
10614                ],
10615                body: stmts.clone(),
10616                is_generator: false,
10617            };
10618            self.env.set(format!("__agent_{name}_on_tool_call__"), hook);
10619        }
10620        if let Some(stmts) = on_complete {
10621            let hook = Value::Function {
10622                name: format!("__agent_{name}_on_complete__"),
10623                params: vec![Param {
10624                    name: "result".into(),
10625                    type_ann: None,
10626                }],
10627                body: stmts.clone(),
10628                is_generator: false,
10629            };
10630            self.env.set(format!("__agent_{name}_on_complete__"), hook);
10631        }
10632
10633        Ok(Signal::None)
10634    }
10635
10636    fn extract_agent_tool(&self, val: &Value) -> (String, serde_json::Value) {
10637        let mut desc = String::new();
10638        let mut params = serde_json::Value::Object(serde_json::Map::new());
10639        if let Value::Map(pairs) = val {
10640            for (key, v) in pairs {
10641                match key.as_str() {
10642                    "description" => {
10643                        if let Value::String(s) = v {
10644                            desc = s.clone();
10645                        }
10646                    }
10647                    "parameters" => {
10648                        params = self.agent_value_to_json(v);
10649                    }
10650                    _ => {}
10651                }
10652            }
10653        }
10654        (desc, params)
10655    }
10656
10657    fn agent_value_to_json(&self, val: &Value) -> serde_json::Value {
10658        match val {
10659            Value::String(s) => serde_json::Value::String(s.clone()),
10660            Value::Int(n) => serde_json::json!(*n),
10661            Value::Float(f) => serde_json::json!(*f),
10662            Value::Bool(b) => serde_json::Value::Bool(*b),
10663            Value::None => serde_json::Value::Null,
10664            Value::List(items) => serde_json::Value::Array(
10665                items.iter().map(|v| self.agent_value_to_json(v)).collect(),
10666            ),
10667            Value::Map(pairs) => {
10668                let mut map = serde_json::Map::new();
10669                for (k, v) in pairs {
10670                    map.insert(k.clone(), self.agent_value_to_json(v));
10671                }
10672                serde_json::Value::Object(map)
10673            }
10674            _ => serde_json::Value::Null,
10675        }
10676    }
10677
10678    fn exec_agent_loop(
10679        &mut self,
10680        agent_def: &tl_stream::AgentDef,
10681        user_message: &str,
10682        history: Option<&[(String, String)]>,
10683    ) -> Result<Value, TlError> {
10684        use tl_ai::{LlmResponse, chat_with_tools, format_tool_result_messages};
10685
10686        let model = &agent_def.model;
10687        let system = agent_def.system_prompt.as_deref();
10688        let base_url = agent_def.base_url.as_deref();
10689        let api_key = agent_def.api_key.as_deref();
10690        let provider = if model.starts_with("claude") {
10691            "anthropic"
10692        } else {
10693            "openai"
10694        };
10695
10696        // Build tools JSON in OpenAI format from TL-declared tools
10697        #[allow(unused_mut)]
10698        let mut tools_json: Vec<serde_json::Value> = agent_def
10699            .tools
10700            .iter()
10701            .map(|t| {
10702                serde_json::json!({
10703                    "type": "function",
10704                    "function": {
10705                        "name": t.name,
10706                        "description": t.description,
10707                        "parameters": t.parameters
10708                    }
10709                })
10710            })
10711            .collect();
10712
10713        // Add MCP tools from connected servers
10714        #[cfg(feature = "mcp")]
10715        let mcp_clients = self
10716            .mcp_agent_clients
10717            .get(&agent_def.name)
10718            .cloned()
10719            .unwrap_or_default();
10720        #[cfg(feature = "mcp")]
10721        let mcp_tool_dispatch: std::collections::HashMap<String, usize> = {
10722            let mut dispatch = std::collections::HashMap::new();
10723            for (client_idx, client) in mcp_clients.iter().enumerate() {
10724                if let Ok(mcp_tools) = client.list_tools() {
10725                    for tool in mcp_tools {
10726                        let tool_name = tool.name.to_string();
10727                        tools_json.push(serde_json::json!({
10728                            "type": "function",
10729                            "function": {
10730                                "name": &tool_name,
10731                                "description": tool.description.as_deref().unwrap_or(""),
10732                                "parameters": serde_json::Value::Object((*tool.input_schema).clone())
10733                            }
10734                        }));
10735                        dispatch.insert(tool_name, client_idx);
10736                    }
10737                }
10738            }
10739            dispatch
10740        };
10741
10742        // Seed messages with history if provided
10743        let mut messages: Vec<serde_json::Value> = Vec::new();
10744        if let Some(hist) = history {
10745            for (role, content) in hist {
10746                messages.push(serde_json::json!({"role": role, "content": content}));
10747            }
10748        }
10749        // Add the current user message
10750        messages.push(serde_json::json!({"role": "user", "content": user_message}));
10751
10752        for turn in 0..agent_def.max_turns {
10753            let response = chat_with_tools(
10754                model,
10755                system,
10756                &messages,
10757                &tools_json,
10758                base_url,
10759                api_key,
10760                agent_def.output_format.as_deref(),
10761            )
10762            .map_err(|e| runtime_err(format!("Agent LLM error: {e}")))?;
10763
10764            match response {
10765                LlmResponse::Text(text) => {
10766                    // Add assistant response to history
10767                    messages.push(serde_json::json!({"role": "assistant", "content": &text}));
10768
10769                    // Build conversation history as list of [role, content] pairs
10770                    let history_list: Vec<Value> = messages
10771                        .iter()
10772                        .filter_map(|m| {
10773                            let role = m["role"].as_str()?;
10774                            let content = m["content"].as_str()?;
10775                            Some(Value::List(vec![
10776                                Value::String(role.to_string()),
10777                                Value::String(content.to_string()),
10778                            ]))
10779                        })
10780                        .collect();
10781
10782                    let result = Value::Map(vec![
10783                        ("response".to_string(), Value::String(text)),
10784                        ("turns".to_string(), Value::Int(turn as i64 + 1)),
10785                        ("history".to_string(), Value::List(history_list)),
10786                    ]);
10787
10788                    // Call on_complete lifecycle hook if defined
10789                    let hook_name = format!("__agent_{}_on_complete__", agent_def.name);
10790                    if let Some(hook) = self.env.get(&hook_name).cloned() {
10791                        let _ = self.call_function_value(&hook, std::slice::from_ref(&result));
10792                    }
10793
10794                    return Ok(result);
10795                }
10796                LlmResponse::ToolUse(tool_calls) => {
10797                    let tc_json: Vec<serde_json::Value> = tool_calls.iter().map(|tc| {
10798                        serde_json::json!({
10799                            "id": tc.id,
10800                            "type": "function",
10801                            "function": {
10802                                "name": tc.name,
10803                                "arguments": serde_json::to_string(&tc.input).unwrap_or_default()
10804                            }
10805                        })
10806                    }).collect();
10807                    messages.push(serde_json::json!({"role": "assistant", "tool_calls": tc_json}));
10808
10809                    // Build declared tool names (TL tools + MCP tools)
10810                    #[allow(unused_mut)]
10811                    let mut declared: Vec<String> =
10812                        agent_def.tools.iter().map(|t| t.name.clone()).collect();
10813                    #[cfg(feature = "mcp")]
10814                    {
10815                        for name in mcp_tool_dispatch.keys() {
10816                            declared.push(name.clone());
10817                        }
10818                    }
10819
10820                    let mut results: Vec<(String, String)> = Vec::new();
10821                    for tc in &tool_calls {
10822                        if !declared.iter().any(|d| d == &tc.name) {
10823                            results.push((
10824                                tc.name.clone(),
10825                                format!("Error: '{}' not in declared tools", tc.name),
10826                            ));
10827                            continue;
10828                        }
10829
10830                        // Try MCP dispatch first, then fall back to TL function lookup
10831                        let result_str;
10832                        #[cfg(feature = "mcp")]
10833                        {
10834                            if let Some(&client_idx) = mcp_tool_dispatch.get(tc.name.as_str()) {
10835                                let mcp_result = mcp_clients[client_idx]
10836                                    .call_tool(&tc.name, tc.input.clone())
10837                                    .map_err(|e| runtime_err(format!("MCP tool error: {e}")))?;
10838                                result_str = mcp_result
10839                                    .content
10840                                    .iter()
10841                                    .filter_map(|c| c.raw.as_text().map(|t| t.text.as_str()))
10842                                    .collect::<Vec<_>>()
10843                                    .join("\n");
10844                            } else {
10845                                let func = self
10846                                    .env
10847                                    .get(&tc.name)
10848                                    .ok_or_else(|| {
10849                                        runtime_err(format!(
10850                                            "Agent tool function '{}' not found",
10851                                            tc.name
10852                                        ))
10853                                    })?
10854                                    .clone();
10855                                let call_args = self.agent_json_to_values(&tc.input);
10856                                let result = self.call_function_value(&func, &call_args)?;
10857                                result_str = format!("{result}");
10858                            }
10859                        }
10860                        #[cfg(not(feature = "mcp"))]
10861                        {
10862                            let func = self
10863                                .env
10864                                .get(&tc.name)
10865                                .ok_or_else(|| {
10866                                    runtime_err(format!(
10867                                        "Agent tool function '{}' not found",
10868                                        tc.name
10869                                    ))
10870                                })?
10871                                .clone();
10872                            let call_args = self.agent_json_to_values(&tc.input);
10873                            let result = self.call_function_value(&func, &call_args)?;
10874                            result_str = format!("{result}");
10875                        }
10876
10877                        // Call on_tool_call lifecycle hook if defined
10878                        let hook_name = format!("__agent_{}_on_tool_call__", agent_def.name);
10879                        if let Some(hook) = self.env.get(&hook_name).cloned() {
10880                            let hook_args = vec![
10881                                Value::String(tc.name.clone()),
10882                                Value::Map(
10883                                    tc.input
10884                                        .as_object()
10885                                        .map(|m| {
10886                                            m.iter()
10887                                                .map(|(k, v)| {
10888                                                    (k.clone(), self.agent_json_to_value(v))
10889                                                })
10890                                                .collect()
10891                                        })
10892                                        .unwrap_or_default(),
10893                                ),
10894                                Value::String(result_str.clone()),
10895                            ];
10896                            let _ = self.call_function_value(&hook, &hook_args);
10897                        }
10898
10899                        results.push((tc.name.clone(), result_str));
10900                    }
10901
10902                    let result_msgs = format_tool_result_messages(provider, &tool_calls, &results);
10903                    messages.extend(result_msgs);
10904                }
10905            }
10906        }
10907
10908        Err(runtime_err(format!(
10909            "Agent '{}' exceeded max_turns ({})",
10910            agent_def.name, agent_def.max_turns
10911        )))
10912    }
10913
10914    fn agent_json_to_values(&self, input: &serde_json::Value) -> Vec<Value> {
10915        match input {
10916            serde_json::Value::Object(map) => {
10917                map.values().map(|v| self.agent_json_to_value(v)).collect()
10918            }
10919            serde_json::Value::Array(arr) => {
10920                arr.iter().map(|v| self.agent_json_to_value(v)).collect()
10921            }
10922            _ => vec![self.agent_json_to_value(input)],
10923        }
10924    }
10925
10926    fn agent_json_to_value(&self, val: &serde_json::Value) -> Value {
10927        match val {
10928            serde_json::Value::String(s) => Value::String(s.clone()),
10929            serde_json::Value::Number(n) => {
10930                if let Some(i) = n.as_i64() {
10931                    Value::Int(i)
10932                } else if let Some(f) = n.as_f64() {
10933                    Value::Float(f)
10934                } else {
10935                    Value::None
10936                }
10937            }
10938            serde_json::Value::Bool(b) => Value::Bool(*b),
10939            serde_json::Value::Null => Value::None,
10940            serde_json::Value::Array(arr) => {
10941                Value::List(arr.iter().map(|v| self.agent_json_to_value(v)).collect())
10942            }
10943            serde_json::Value::Object(map) => Value::Map(
10944                map.iter()
10945                    .map(|(k, v)| (k.clone(), self.agent_json_to_value(v)))
10946                    .collect(),
10947            ),
10948        }
10949    }
10950
10951    fn call_function_value(&mut self, func: &Value, args: &[Value]) -> Result<Value, TlError> {
10952        match func {
10953            Value::Function {
10954                params,
10955                body,
10956                is_generator,
10957                ..
10958            } => {
10959                if *is_generator {
10960                    return self.create_generator(params, body, args);
10961                }
10962                self.env.push_scope();
10963                for (param, arg) in params.iter().zip(args.iter()) {
10964                    self.env.set(param.name.clone(), arg.clone());
10965                }
10966                let mut result = Value::None;
10967                for stmt in body {
10968                    match self.exec_stmt(stmt)? {
10969                        Signal::Return(v) => {
10970                            result = v;
10971                            break;
10972                        }
10973                        Signal::None => {}
10974                        _ => {}
10975                    }
10976                }
10977                self.env.pop_scope();
10978                Ok(result)
10979            }
10980            Value::Closure {
10981                params,
10982                body,
10983                captured_env,
10984            } => {
10985                let saved_env = std::mem::replace(&mut self.env.scopes, captured_env.clone());
10986                self.env.push_scope();
10987                for (param, arg) in params.iter().zip(args.iter()) {
10988                    self.env.set(param.name.clone(), arg.clone());
10989                }
10990                let result = match body {
10991                    ClosureBody::Expr(e) => self.eval_expr(e),
10992                    ClosureBody::Block { stmts, expr } => {
10993                        let mut early_return = None;
10994                        for s in stmts {
10995                            match self.exec_stmt(s) {
10996                                Ok(Signal::Return(val)) => {
10997                                    early_return = Some(val);
10998                                    break;
10999                                }
11000                                Ok(_) => {}
11001                                Err(e) => {
11002                                    self.env.scopes = saved_env;
11003                                    return Err(e);
11004                                }
11005                            }
11006                        }
11007                        if let Some(val) = early_return {
11008                            Ok(val)
11009                        } else if let Some(e) = expr {
11010                            self.eval_expr(e)
11011                        } else {
11012                            Ok(Value::None)
11013                        }
11014                    }
11015                };
11016                self.env.scopes = saved_env;
11017                result
11018            }
11019            Value::Builtin(name) => self.call_builtin(name, args),
11020            _ => Err(runtime_err(format!(
11021                "Agent tool is not callable: {}",
11022                func.type_name()
11023            ))),
11024        }
11025    }
11026
11027    fn exec_source_decl(
11028        &mut self,
11029        name: &str,
11030        connector_type: &str,
11031        config: &[(String, Expr)],
11032    ) -> Result<Signal, TlError> {
11033        let mut properties = std::collections::HashMap::new();
11034        for (key, expr) in config {
11035            let val = self.eval_expr(expr)?;
11036            properties.insert(key.clone(), format!("{val}"));
11037        }
11038
11039        let config = ConnectorConfig {
11040            name: name.to_string(),
11041            connector_type: connector_type.to_string(),
11042            properties,
11043        };
11044
11045        self.env.set(name.to_string(), Value::Connector(config));
11046        Ok(Signal::None)
11047    }
11048
11049    fn exec_sink_decl(
11050        &mut self,
11051        name: &str,
11052        connector_type: &str,
11053        config: &[(String, Expr)],
11054    ) -> Result<Signal, TlError> {
11055        let mut properties = std::collections::HashMap::new();
11056        for (key, expr) in config {
11057            let val = self.eval_expr(expr)?;
11058            properties.insert(key.clone(), format!("{val}"));
11059        }
11060
11061        let config = ConnectorConfig {
11062            name: name.to_string(),
11063            connector_type: connector_type.to_string(),
11064            properties,
11065        };
11066
11067        self.env.set(name.to_string(), Value::Connector(config));
11068        Ok(Signal::None)
11069    }
11070
11071    // --- Phase 20: Python FFI builtins (interpreter) ---
11072
11073    #[cfg(feature = "python")]
11074    fn interp_py_import(&mut self, args: &[Value]) -> Result<Value, TlError> {
11075        use pyo3::prelude::*;
11076        if args.is_empty() {
11077            return Err(runtime_err_s("py_import() expects a module name"));
11078        }
11079        let name = match &args[0] {
11080            Value::String(s) => s.clone(),
11081            _ => return Err(runtime_err_s("py_import() expects a string module name")),
11082        };
11083        pyo3::Python::with_gil(|py| {
11084            let module = py
11085                .import(&*name)
11086                .map_err(|e| runtime_err(format!("py_import('{name}'): {e}")))?;
11087            Ok(Value::PyObject(Arc::new(InterpPyObjectWrapper {
11088                inner: module.into_any().unbind(),
11089            })))
11090        })
11091    }
11092
11093    #[cfg(feature = "python")]
11094    fn interp_py_eval(&mut self, args: &[Value]) -> Result<Value, TlError> {
11095        use pyo3::prelude::*;
11096        if args.is_empty() {
11097            return Err(runtime_err_s("py_eval() expects a code string"));
11098        }
11099        let code = match &args[0] {
11100            Value::String(s) => s.clone(),
11101            _ => return Err(runtime_err_s("py_eval() expects a string")),
11102        };
11103        pyo3::Python::with_gil(|py| {
11104            let result = py
11105                .eval(&std::ffi::CString::new(code.as_str()).unwrap(), None, None)
11106                .map_err(|e| runtime_err(format!("py_eval(): {e}")))?;
11107            interp_py_to_value(py, &result)
11108                .map_err(|e| runtime_err(format!("py_eval() conversion: {e}")))
11109        })
11110    }
11111
11112    #[cfg(feature = "python")]
11113    fn interp_py_call(&mut self, args: &[Value]) -> Result<Value, TlError> {
11114        use pyo3::prelude::*;
11115        if args.is_empty() {
11116            return Err(runtime_err_s("py_call() expects a callable and arguments"));
11117        }
11118        let callable = match &args[0] {
11119            Value::PyObject(w) => w.clone(),
11120            _ => {
11121                return Err(runtime_err_s(
11122                    "py_call() first argument must be a Python object",
11123                ));
11124            }
11125        };
11126        let call_args = &args[1..];
11127        pyo3::Python::with_gil(|py| {
11128            let py_args: Vec<pyo3::Py<pyo3::PyAny>> = call_args
11129                .iter()
11130                .map(|a| interp_value_to_py(py, a))
11131                .collect::<Result<_, _>>()
11132                .map_err(|e| runtime_err(format!("py_call() arg conversion: {e}")))?;
11133            let tuple = pyo3::types::PyTuple::new(py, &py_args)
11134                .map_err(|e| runtime_err(format!("py_call() tuple: {e}")))?;
11135            let result = callable
11136                .inner
11137                .call1(py, tuple)
11138                .map_err(|e| runtime_err(format!("py_call(): {e}")))?;
11139            interp_py_to_value(py, result.bind(py))
11140                .map_err(|e| runtime_err(format!("py_call() result conversion: {e}")))
11141        })
11142    }
11143
11144    #[cfg(feature = "python")]
11145    fn interp_py_getattr(&mut self, args: &[Value]) -> Result<Value, TlError> {
11146        use pyo3::prelude::*;
11147        if args.len() < 2 {
11148            return Err(runtime_err_s("py_getattr() expects (object, name)"));
11149        }
11150        let obj = match &args[0] {
11151            Value::PyObject(w) => w.clone(),
11152            _ => {
11153                return Err(runtime_err_s(
11154                    "py_getattr() first argument must be a Python object",
11155                ));
11156            }
11157        };
11158        let attr_name = match &args[1] {
11159            Value::String(s) => s.clone(),
11160            _ => {
11161                return Err(runtime_err_s(
11162                    "py_getattr() second argument must be a string",
11163                ));
11164            }
11165        };
11166        pyo3::Python::with_gil(|py| {
11167            let bound = obj.inner.bind(py);
11168            let attr = bound
11169                .getattr(attr_name.as_str())
11170                .map_err(|e| runtime_err(format!("py_getattr('{attr_name}'): {e}")))?;
11171            interp_py_to_value(py, &attr)
11172                .map_err(|e| runtime_err(format!("py_getattr() conversion: {e}")))
11173        })
11174    }
11175
11176    #[cfg(feature = "python")]
11177    fn interp_py_setattr(&mut self, args: &[Value]) -> Result<Value, TlError> {
11178        use pyo3::prelude::*;
11179        if args.len() < 3 {
11180            return Err(runtime_err_s("py_setattr() expects (object, name, value)"));
11181        }
11182        let obj = match &args[0] {
11183            Value::PyObject(w) => w.clone(),
11184            _ => {
11185                return Err(runtime_err_s(
11186                    "py_setattr() first argument must be a Python object",
11187                ));
11188            }
11189        };
11190        let attr_name = match &args[1] {
11191            Value::String(s) => s.clone(),
11192            _ => {
11193                return Err(runtime_err_s(
11194                    "py_setattr() second argument must be a string",
11195                ));
11196            }
11197        };
11198        pyo3::Python::with_gil(|py| {
11199            let py_val = interp_value_to_py(py, &args[2])
11200                .map_err(|e| runtime_err(format!("py_setattr() conversion: {e}")))?;
11201            obj.inner
11202                .bind(py)
11203                .setattr(attr_name.as_str(), py_val)
11204                .map_err(|e| runtime_err(format!("py_setattr('{attr_name}'): {e}")))?;
11205            Ok(Value::None)
11206        })
11207    }
11208
11209    #[cfg(feature = "python")]
11210    fn interp_py_to_tl(&mut self, args: &[Value]) -> Result<Value, TlError> {
11211        use pyo3::prelude::*;
11212        if args.is_empty() {
11213            return Err(runtime_err_s("py_to_tl() expects a Python object"));
11214        }
11215        match &args[0] {
11216            Value::PyObject(w) => pyo3::Python::with_gil(|py| {
11217                let bound = w.inner.bind(py);
11218                interp_py_to_value(py, &bound).map_err(|e| runtime_err(format!("py_to_tl(): {e}")))
11219            }),
11220            other => Ok(other.clone()),
11221        }
11222    }
11223}
11224
11225// --- Phase 20: Python FFI value conversion for interpreter ---
11226
11227#[cfg(feature = "python")]
11228fn interp_value_to_py(py: pyo3::Python<'_>, val: &Value) -> pyo3::PyResult<pyo3::Py<pyo3::PyAny>> {
11229    use pyo3::prelude::*;
11230    use pyo3::types::{PyDict, PyList, PySet};
11231
11232    match val {
11233        Value::Int(n) => Ok((*n).into_pyobject(py)?.into_any().into()),
11234        Value::Float(f) => Ok((*f).into_pyobject(py)?.into_any().unbind()),
11235        Value::String(s) => Ok(s.as_str().into_pyobject(py)?.into_any().unbind()),
11236        Value::Bool(b) => Ok((*b).into_pyobject(py)?.to_owned().into_any().unbind()),
11237        Value::None => Ok(py.None()),
11238        Value::List(items) => {
11239            let py_items: Vec<pyo3::Py<pyo3::PyAny>> = items
11240                .iter()
11241                .map(|item| interp_value_to_py(py, item))
11242                .collect::<pyo3::PyResult<_>>()?;
11243            Ok(PyList::new(py, &py_items)?.into_any().unbind())
11244        }
11245        Value::Map(pairs) => {
11246            let dict = PyDict::new(py);
11247            for (k, v) in pairs {
11248                let py_val = interp_value_to_py(py, v)?;
11249                dict.set_item(k.as_str(), py_val)?;
11250            }
11251            Ok(dict.into_any().unbind())
11252        }
11253        Value::Set(items) => {
11254            let py_items: Vec<pyo3::Py<pyo3::PyAny>> = items
11255                .iter()
11256                .map(|item| interp_value_to_py(py, item))
11257                .collect::<pyo3::PyResult<_>>()?;
11258            Ok(PySet::new(py, &py_items)?.into_any().unbind())
11259        }
11260        Value::PyObject(w) => Ok(w.inner.clone_ref(py)),
11261        _ => Err(pyo3::exceptions::PyTypeError::new_err(format!(
11262            "Cannot convert TL {} to Python",
11263            val.type_name()
11264        ))),
11265    }
11266}
11267
11268#[cfg(feature = "python")]
11269fn interp_py_to_value(
11270    py: pyo3::Python<'_>,
11271    obj: &pyo3::Bound<'_, pyo3::PyAny>,
11272) -> pyo3::PyResult<Value> {
11273    use pyo3::prelude::*;
11274    use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyList, PySet, PyString};
11275
11276    if obj.is_instance_of::<PyBool>() {
11277        return Ok(Value::Bool(obj.extract::<bool>()?));
11278    }
11279    if obj.is_instance_of::<PyInt>() {
11280        return Ok(Value::Int(obj.extract::<i64>()?));
11281    }
11282    if obj.is_instance_of::<PyFloat>() {
11283        return Ok(Value::Float(obj.extract::<f64>()?));
11284    }
11285    if obj.is_instance_of::<PyString>() {
11286        return Ok(Value::String(obj.extract::<String>()?));
11287    }
11288    if obj.is_none() {
11289        return Ok(Value::None);
11290    }
11291    if obj.is_instance_of::<PyList>() {
11292        let list = obj.downcast::<PyList>()?;
11293        let items: Vec<Value> = list
11294            .iter()
11295            .map(|item| interp_py_to_value(py, &item))
11296            .collect::<pyo3::PyResult<_>>()?;
11297        return Ok(Value::List(items));
11298    }
11299    if obj.is_instance_of::<PyDict>() {
11300        let dict = obj.downcast::<PyDict>()?;
11301        let mut pairs = Vec::new();
11302        for (k, v) in dict.iter() {
11303            let key: String = k.extract()?;
11304            let val = interp_py_to_value(py, &v)?;
11305            pairs.push((key, val));
11306        }
11307        return Ok(Value::Map(pairs));
11308    }
11309    if obj.is_instance_of::<PySet>() {
11310        let set = obj.downcast::<PySet>()?;
11311        let mut items = Vec::new();
11312        for item in set.iter() {
11313            items.push(interp_py_to_value(py, &item)?);
11314        }
11315        return Ok(Value::Set(items));
11316    }
11317
11318    // Everything else stays as opaque PyObject
11319    Ok(Value::PyObject(Arc::new(InterpPyObjectWrapper {
11320        inner: obj.clone().unbind(),
11321    })))
11322}
11323
11324#[cfg(feature = "python")]
11325fn interp_py_get_member(wrapper: &InterpPyObjectWrapper, field: &str) -> Value {
11326    use pyo3::prelude::*;
11327    pyo3::Python::with_gil(|py| {
11328        let bound = wrapper.inner.bind(py);
11329        match bound.getattr(field) {
11330            Ok(attr) => interp_py_to_value(py, &attr).unwrap_or(Value::None),
11331            Err(_) => Value::None,
11332        }
11333    })
11334}
11335
11336#[cfg(feature = "python")]
11337fn interp_py_call_method(
11338    wrapper: &InterpPyObjectWrapper,
11339    method: &str,
11340    args: &[Value],
11341) -> Result<Value, TlError> {
11342    use pyo3::prelude::*;
11343    pyo3::Python::with_gil(|py| {
11344        let bound = wrapper.inner.bind(py);
11345        let py_args: Vec<pyo3::Py<pyo3::PyAny>> = args
11346            .iter()
11347            .map(|a| interp_value_to_py(py, a))
11348            .collect::<Result<_, _>>()
11349            .map_err(|e| runtime_err(format!("Python arg conversion: {e}")))?;
11350        let tuple = pyo3::types::PyTuple::new(py, &py_args)
11351            .map_err(|e| runtime_err(format!("Python tuple: {e}")))?;
11352        let attr = bound
11353            .getattr(method)
11354            .map_err(|e| runtime_err(format!("Python: no attribute '{method}': {e}")))?;
11355        let result = attr
11356            .call1(tuple)
11357            .map_err(|e| runtime_err(format!("Python method '{method}': {e}")))?;
11358        interp_py_to_value(py, &result)
11359            .map_err(|e| runtime_err(format!("Python result conversion: {e}")))
11360    })
11361}
11362
11363#[cfg(test)]
11364mod tests {
11365    use super::*;
11366    use tl_parser::parse;
11367
11368    fn run(source: &str) -> Result<Value, TlError> {
11369        let program = parse(source)?;
11370        let mut interp = Interpreter::new();
11371        interp.execute(&program)
11372    }
11373
11374    fn run_output(source: &str) -> Vec<String> {
11375        let program = parse(source).unwrap();
11376        let mut interp = Interpreter::new();
11377        interp.execute(&program).unwrap();
11378        interp.output
11379    }
11380
11381    fn run_err(source: &str) -> String {
11382        let program = parse(source).unwrap();
11383        let mut interp = Interpreter::new();
11384        match interp.execute(&program) {
11385            Err(e) => format!("{e}"),
11386            Ok(_) => "no error".to_string(),
11387        }
11388    }
11389
11390    #[test]
11391    fn test_arithmetic() {
11392        assert!(matches!(run("1 + 2").unwrap(), Value::Int(3)));
11393        assert!(matches!(run("10 - 3").unwrap(), Value::Int(7)));
11394        assert!(matches!(run("4 * 5").unwrap(), Value::Int(20)));
11395        assert!(matches!(run("10 / 3").unwrap(), Value::Int(3)));
11396        assert!(matches!(run("10 % 3").unwrap(), Value::Int(1)));
11397        assert!(matches!(run("2 ** 10").unwrap(), Value::Int(1024)));
11398    }
11399
11400    #[test]
11401    fn test_precedence() {
11402        assert!(matches!(run("2 + 3 * 4").unwrap(), Value::Int(14)));
11403        assert!(matches!(run("(2 + 3) * 4").unwrap(), Value::Int(20)));
11404    }
11405
11406    #[test]
11407    fn test_let_and_variable() {
11408        let output = run_output("let x = 42\nprint(x)");
11409        assert_eq!(output, vec!["42"]);
11410    }
11411
11412    #[test]
11413    fn test_function() {
11414        let output = run_output(
11415            "fn double(n: int64) -> int64 { n * 2 }\nlet result = double(21)\nprint(result)",
11416        );
11417        assert_eq!(output, vec!["42"]);
11418    }
11419
11420    #[test]
11421    fn test_pipe() {
11422        let output =
11423            run_output("fn double(n: int64) -> int64 { n * 2 }\nlet x = 5 |> double()\nprint(x)");
11424        assert_eq!(output, vec!["10"]);
11425    }
11426
11427    #[test]
11428    fn test_if_else() {
11429        let output =
11430            run_output("let x = 10\nif x > 5 { print(\"big\") } else { print(\"small\") }");
11431        assert_eq!(output, vec!["big"]);
11432    }
11433
11434    #[test]
11435    fn test_string_interpolation() {
11436        let output = run_output("let name = \"TL\"\nprint(\"Hello {name}!\")");
11437        assert_eq!(output, vec!["Hello TL!"]);
11438    }
11439
11440    #[test]
11441    fn test_list() {
11442        let output = run_output("let items = [1, 2, 3]\nprint(len(items))");
11443        assert_eq!(output, vec!["3"]);
11444    }
11445
11446    #[test]
11447    fn test_comparison() {
11448        assert!(matches!(run("5 > 3").unwrap(), Value::Bool(true)));
11449        assert!(matches!(run("5 < 3").unwrap(), Value::Bool(false)));
11450        assert!(matches!(run("5 == 5").unwrap(), Value::Bool(true)));
11451    }
11452
11453    #[test]
11454    fn test_match_int() {
11455        let output =
11456            run_output("let x = 2\nprint(match x { 1 => \"one\", 2 => \"two\", _ => \"other\" })");
11457        assert_eq!(output, vec!["two"]);
11458    }
11459
11460    #[test]
11461    fn test_match_wildcard() {
11462        let output = run_output("let x = 99\nprint(match x { 1 => \"one\", _ => \"fallback\" })");
11463        assert_eq!(output, vec!["fallback"]);
11464    }
11465
11466    #[test]
11467    fn test_match_string() {
11468        let output =
11469            run_output("let s = \"hi\"\nprint(match s { \"hello\" => 1, \"hi\" => 2, _ => 0 })");
11470        assert_eq!(output, vec!["2"]);
11471    }
11472
11473    #[test]
11474    fn test_closure() {
11475        let output = run_output("let double = (x) => x * 2\nprint(double(5))");
11476        assert_eq!(output, vec!["10"]);
11477    }
11478
11479    #[test]
11480    fn test_closure_capture() {
11481        let output = run_output("let factor = 3\nlet mul = (x) => x * factor\nprint(mul(7))");
11482        assert_eq!(output, vec!["21"]);
11483    }
11484
11485    #[test]
11486    fn test_for_loop() {
11487        let output = run_output("let sum = 0\nfor i in range(5) { sum = sum + i }\nprint(sum)");
11488        assert_eq!(output, vec!["10"]);
11489    }
11490
11491    #[test]
11492    fn test_map_builtin() {
11493        let output = run_output(
11494            "let nums = [1, 2, 3]\nlet doubled = map(nums, (x) => x * 2)\nprint(doubled)",
11495        );
11496        assert_eq!(output, vec!["[2, 4, 6]"]);
11497    }
11498
11499    #[test]
11500    fn test_filter_builtin() {
11501        let output = run_output(
11502            "let nums = [1, 2, 3, 4, 5]\nlet evens = filter(nums, (x) => x % 2 == 0)\nprint(evens)",
11503        );
11504        assert_eq!(output, vec!["[2, 4]"]);
11505    }
11506
11507    #[test]
11508    fn test_pipe_with_closure() {
11509        let output = run_output("let result = [1, 2, 3] |> map((x) => x + 10)\nprint(result)");
11510        assert_eq!(output, vec!["[11, 12, 13]"]);
11511    }
11512
11513    #[test]
11514    fn test_sum_builtin() {
11515        let output = run_output("print(sum([1, 2, 3, 4]))");
11516        assert_eq!(output, vec!["10"]);
11517    }
11518
11519    #[test]
11520    fn test_reduce_builtin() {
11521        let output = run_output(
11522            "let product = reduce([1, 2, 3, 4], 1, (acc, x) => acc * x)\nprint(product)",
11523        );
11524        assert_eq!(output, vec!["24"]);
11525    }
11526
11527    #[test]
11528    fn test_struct_creation() {
11529        let output = run_output(
11530            "struct Point { x: float64, y: float64 }\nlet p = Point { x: 1.0, y: 2.0 }\nprint(p.x)\nprint(p.y)",
11531        );
11532        assert_eq!(output, vec!["1.0", "2.0"]);
11533    }
11534
11535    #[test]
11536    fn test_struct_nested() {
11537        let output = run_output(
11538            "struct Point { x: float64, y: float64 }\nstruct Line { start: Point, end_pt: Point }\nlet l = Line { start: Point { x: 0.0, y: 0.0 }, end_pt: Point { x: 1.0, y: 1.0 } }\nprint(l.start.x)",
11539        );
11540        assert_eq!(output, vec!["0.0"]);
11541    }
11542
11543    #[test]
11544    fn test_enum_creation() {
11545        let output = run_output("enum Color { Red, Green, Blue }\nlet c = Color::Red\nprint(c)");
11546        assert!(output.len() == 1);
11547        assert!(
11548            output[0].contains("Color::Red"),
11549            "expected output to contain 'Color::Red', got: {}",
11550            output[0]
11551        );
11552    }
11553
11554    #[test]
11555    fn test_enum_with_fields() {
11556        let output = run_output(
11557            "enum Shape { Circle(float64), Rect(float64, float64) }\nlet s = Shape::Circle(5.0)\nprint(s)",
11558        );
11559        assert!(output.len() == 1);
11560        assert!(
11561            output[0].contains("Circle"),
11562            "expected output to contain 'Circle', got: {}",
11563            output[0]
11564        );
11565    }
11566
11567    #[test]
11568    fn test_enum_match() {
11569        let output = run_output(
11570            "enum Shape { Circle(float64), Rect(float64, float64) }\nlet s = Shape::Circle(5.0)\nlet result = match s {\n    Shape::Circle(r) => r * 2.0,\n    Shape::Rect(w, h) => w * h,\n    _ => 0.0\n}\nprint(result)",
11571        );
11572        assert_eq!(output, vec!["10.0"]);
11573    }
11574
11575    #[test]
11576    fn test_impl_method() {
11577        let output = run_output(
11578            "struct Counter { value: int64 }\nimpl Counter {\n    fn get(self) { self.value }\n}\nlet c = Counter { value: 42 }\nprint(c.get())",
11579        );
11580        assert_eq!(output, vec!["42"]);
11581    }
11582
11583    #[test]
11584    fn test_try_catch() {
11585        let output = run_output("try {\n    throw \"oops\"\n} catch e {\n    print(e)\n}");
11586        assert_eq!(output, vec!["oops"]);
11587    }
11588
11589    #[test]
11590    fn test_try_catch_runtime_error() {
11591        let output = run_output("try {\n    let x = 1 / 0\n} catch e {\n    print(\"caught\")\n}");
11592        assert_eq!(output, vec!["caught"]);
11593    }
11594
11595    #[test]
11596    fn test_string_methods() {
11597        let output = run_output(
11598            "print(\"hello world\".split(\" \"))\nprint(\"  hello  \".trim())\nprint(\"hello\".contains(\"ell\"))\nprint(\"hello\".to_upper())",
11599        );
11600        assert_eq!(output, vec!["[hello, world]", "hello", "true", "HELLO"]);
11601    }
11602
11603    #[test]
11604    fn test_math_builtins() {
11605        let output =
11606            run_output("print(sqrt(16.0))\nprint(floor(3.7))\nprint(ceil(3.2))\nprint(abs(-5))");
11607        assert_eq!(output, vec!["4.0", "3.0", "4.0", "5"]);
11608    }
11609
11610    #[test]
11611    fn test_assert_pass() {
11612        let result = run("assert(true)\nassert_eq(1 + 1, 2)");
11613        assert!(
11614            result.is_ok(),
11615            "assert(true) and assert_eq(1+1, 2) should not error"
11616        );
11617    }
11618
11619    #[test]
11620    fn test_assert_fail() {
11621        let result = run("assert(false)");
11622        assert!(result.is_err(), "assert(false) should return an error");
11623    }
11624
11625    #[test]
11626    fn test_join_builtin() {
11627        let output = run_output("print(join(\", \", [\"a\", \"b\", \"c\"]))");
11628        assert_eq!(output, vec!["a, b, c"]);
11629    }
11630
11631    #[test]
11632    fn test_list_methods() {
11633        let output = run_output(
11634            "let nums = [1, 2, 3]\nprint(nums.len())\nlet doubled = nums.map((x) => x * 2)\nprint(doubled)",
11635        );
11636        assert_eq!(output, vec!["3", "[2, 4, 6]"]);
11637    }
11638
11639    // ── Phase 6: Stdlib & Ecosystem tests ──
11640
11641    #[test]
11642    fn test_json_parse_object() {
11643        // Build JSON from map_from + json_stringify to avoid string escaping issues
11644        let output = run_output(
11645            r#"let m = map_from("a", 1, "b", "hello")
11646let s = json_stringify(m)
11647let m2 = json_parse(s)
11648print(m2["a"])
11649print(m2["b"])"#,
11650        );
11651        assert_eq!(output, vec!["1", "hello"]);
11652    }
11653
11654    #[test]
11655    fn test_json_parse_array() {
11656        let output = run_output(
11657            r#"let arr = json_parse("[1, 2, 3]")
11658print(len(arr))"#,
11659        );
11660        assert_eq!(output, vec!["3"]);
11661    }
11662
11663    #[test]
11664    fn test_json_stringify() {
11665        let output = run_output(
11666            r#"let m = map_from("x", 1, "y", 2)
11667let s = json_stringify(m)
11668print(s)"#,
11669        );
11670        assert_eq!(output, vec![r#"{"x":1,"y":2}"#]);
11671    }
11672
11673    #[test]
11674    fn test_json_roundtrip() {
11675        let output = run_output(
11676            r#"let m = map_from("name", "test", "val", 42)
11677let s = json_stringify(m)
11678let m2 = json_parse(s)
11679print(m2["name"])
11680print(m2["val"])"#,
11681        );
11682        assert_eq!(output, vec!["test", "42"]);
11683    }
11684
11685    #[test]
11686    fn test_map_from() {
11687        let output = run_output(
11688            r#"let m = map_from("a", 1, "b", 2)
11689print(m["a"])
11690print(m["b"])"#,
11691        );
11692        assert_eq!(output, vec!["1", "2"]);
11693    }
11694
11695    #[test]
11696    fn test_map_member_access() {
11697        let output = run_output(
11698            r#"let m = map_from("name", "alice")
11699print(m.name)"#,
11700        );
11701        assert_eq!(output, vec!["alice"]);
11702    }
11703
11704    #[test]
11705    fn test_map_index_set() {
11706        let output = run_output(
11707            r#"let m = map_from("a", 1)
11708m["b"] = 2
11709print(m["b"])"#,
11710        );
11711        assert_eq!(output, vec!["2"]);
11712    }
11713
11714    #[test]
11715    fn test_map_methods() {
11716        let output = run_output(
11717            r#"let m = map_from("a", 1, "b", 2, "c", 3)
11718print(m.len())
11719print(m.keys())
11720print(m.contains_key("b"))
11721print(m.contains_key("x"))
11722let m2 = m.remove("b")
11723print(m2.len())"#,
11724        );
11725        assert_eq!(output, vec!["3", "[a, b, c]", "true", "false", "2"]);
11726    }
11727
11728    #[test]
11729    fn test_map_iteration() {
11730        let output = run_output(
11731            r#"let m = map_from("x", 10, "y", 20)
11732for kv in m {
11733    print(kv[0])
11734}"#,
11735        );
11736        assert_eq!(output, vec!["x", "y"]);
11737    }
11738
11739    #[test]
11740    fn test_map_len_type_of() {
11741        let output = run_output(
11742            r#"let m = map_from("a", 1)
11743print(len(m))
11744print(type_of(m))"#,
11745        );
11746        assert_eq!(output, vec!["1", "map"]);
11747    }
11748
11749    #[test]
11750    fn test_file_read_write() {
11751        let output = run_output(
11752            r#"write_file("/tmp/tl_test_phase6.txt", "hello world")
11753let content = read_file("/tmp/tl_test_phase6.txt")
11754print(content)
11755print(file_exists("/tmp/tl_test_phase6.txt"))"#,
11756        );
11757        assert_eq!(output, vec!["hello world", "true"]);
11758    }
11759
11760    #[test]
11761    fn test_file_append() {
11762        let output = run_output(
11763            r#"write_file("/tmp/tl_test_append.txt", "line1")
11764append_file("/tmp/tl_test_append.txt", "line2")
11765let content = read_file("/tmp/tl_test_append.txt")
11766print(content)"#,
11767        );
11768        assert_eq!(output, vec!["line1line2"]);
11769    }
11770
11771    #[test]
11772    fn test_file_exists_false() {
11773        let output = run_output(r#"print(file_exists("/tmp/nonexistent_tl_file_xyz"))"#);
11774        assert_eq!(output, vec!["false"]);
11775    }
11776
11777    #[test]
11778    fn test_list_dir() {
11779        // Setup directory for test
11780        std::fs::create_dir_all("/tmp/tl_listdir_test").ok();
11781        std::fs::write("/tmp/tl_listdir_test/a.txt", "a").ok();
11782        std::fs::write("/tmp/tl_listdir_test/b.txt", "b").ok();
11783        let output = run_output(
11784            r#"let files = list_dir("/tmp/tl_listdir_test")
11785print(len(files) >= 2)"#,
11786        );
11787        assert_eq!(output, vec!["true"]);
11788    }
11789
11790    #[test]
11791    fn test_env_get_set() {
11792        let output = run_output(
11793            r#"env_set("TL_TEST_VAR", "hello123")
11794let v = env_get("TL_TEST_VAR")
11795print(v)"#,
11796        );
11797        assert_eq!(output, vec!["hello123"]);
11798    }
11799
11800    #[test]
11801    fn test_env_get_missing() {
11802        let output = run_output(
11803            r#"let v = env_get("TL_NONEXISTENT_VAR_XYZ")
11804print(v)"#,
11805        );
11806        assert_eq!(output, vec!["none"]);
11807    }
11808
11809    #[test]
11810    fn test_regex_match() {
11811        let output = run_output(
11812            r#"print(regex_match("\\d+", "abc123"))
11813print(regex_match("^\\d+$", "abc123"))"#,
11814        );
11815        assert_eq!(output, vec!["true", "false"]);
11816    }
11817
11818    #[test]
11819    fn test_regex_find() {
11820        let output = run_output(
11821            r#"let matches = regex_find("\\d+", "abc123def456")
11822print(len(matches))
11823print(matches[0])
11824print(matches[1])"#,
11825        );
11826        assert_eq!(output, vec!["2", "123", "456"]);
11827    }
11828
11829    #[test]
11830    fn test_regex_replace() {
11831        let output = run_output(
11832            r#"let result = regex_replace("\\d+", "abc123def456", "X")
11833print(result)"#,
11834        );
11835        assert_eq!(output, vec!["abcXdefX"]);
11836    }
11837
11838    #[test]
11839    fn test_now() {
11840        let output = run_output("let t = now()\nprint(t > 0)");
11841        assert_eq!(output, vec!["true"]);
11842    }
11843
11844    #[test]
11845    fn test_date_format() {
11846        // 2024-01-01 00:00:00 UTC = 1704067200000 ms
11847        let output = run_output(r#"print(date_format(1704067200000, "%Y-%m-%d"))"#);
11848        assert_eq!(output, vec!["2024-01-01"]);
11849    }
11850
11851    #[test]
11852    fn test_date_parse() {
11853        let output = run_output(
11854            r#"let ts = date_parse("2024-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
11855print(ts)"#,
11856        );
11857        assert_eq!(output, vec!["1704067200000"]);
11858    }
11859
11860    #[test]
11861    fn test_string_chars() {
11862        let output = run_output(
11863            r#"let chars = "hello".chars()
11864print(len(chars))
11865print(chars[0])"#,
11866        );
11867        assert_eq!(output, vec!["5", "h"]);
11868    }
11869
11870    #[test]
11871    fn test_string_repeat() {
11872        let output = run_output(r#"print("ab".repeat(3))"#);
11873        assert_eq!(output, vec!["ababab"]);
11874    }
11875
11876    #[test]
11877    fn test_string_index_of() {
11878        let output = run_output(
11879            r#"print("hello world".index_of("world"))
11880print("hello".index_of("xyz"))"#,
11881        );
11882        assert_eq!(output, vec!["6", "-1"]);
11883    }
11884
11885    #[test]
11886    fn test_string_substring() {
11887        let output = run_output(r#"print("hello world".substring(0, 5))"#);
11888        assert_eq!(output, vec!["hello"]);
11889    }
11890
11891    #[test]
11892    fn test_string_pad() {
11893        let output = run_output(
11894            r#"print("42".pad_left(5, "0"))
11895print("hi".pad_right(5, "."))"#,
11896        );
11897        assert_eq!(output, vec!["00042", "hi..."]);
11898    }
11899
11900    #[test]
11901    fn test_list_sort() {
11902        let output = run_output(r#"print([3, 1, 2].sort())"#);
11903        assert_eq!(output, vec!["[1, 2, 3]"]);
11904    }
11905
11906    #[test]
11907    fn test_list_reverse() {
11908        let output = run_output(r#"print([1, 2, 3].reverse())"#);
11909        assert_eq!(output, vec!["[3, 2, 1]"]);
11910    }
11911
11912    #[test]
11913    fn test_list_contains() {
11914        let output = run_output(
11915            r#"print([1, 2, 3].contains(2))
11916print([1, 2, 3].contains(5))"#,
11917        );
11918        assert_eq!(output, vec!["true", "false"]);
11919    }
11920
11921    #[test]
11922    fn test_list_index_of() {
11923        let output = run_output(
11924            r#"print([10, 20, 30].index_of(20))
11925print([10, 20, 30].index_of(99))"#,
11926        );
11927        assert_eq!(output, vec!["1", "-1"]);
11928    }
11929
11930    #[test]
11931    fn test_list_slice() {
11932        let output = run_output(r#"print([1, 2, 3, 4, 5].slice(1, 4))"#);
11933        assert_eq!(output, vec!["[2, 3, 4]"]);
11934    }
11935
11936    #[test]
11937    fn test_list_flat_map() {
11938        let output = run_output(
11939            r#"let result = [1, 2, 3].flat_map((x) => [x, x * 10])
11940print(result)"#,
11941        );
11942        assert_eq!(output, vec!["[1, 10, 2, 20, 3, 30]"]);
11943    }
11944
11945    #[test]
11946    fn test_zip() {
11947        let output = run_output(
11948            r#"let pairs = zip([1, 2, 3], ["a", "b", "c"])
11949print(pairs[0])
11950print(pairs[1])"#,
11951        );
11952        assert_eq!(output, vec!["[1, a]", "[2, b]"]);
11953    }
11954
11955    #[test]
11956    fn test_enumerate() {
11957        let output = run_output(
11958            r#"let items = enumerate(["a", "b", "c"])
11959print(items[0])
11960print(items[2])"#,
11961        );
11962        assert_eq!(output, vec!["[0, a]", "[2, c]"]);
11963    }
11964
11965    #[test]
11966    fn test_bool_builtin() {
11967        let output = run_output(
11968            r#"print(bool(1))
11969print(bool(0))
11970print(bool(""))
11971print(bool("hello"))"#,
11972        );
11973        assert_eq!(output, vec!["true", "false", "false", "true"]);
11974    }
11975
11976    #[test]
11977    fn test_range_step() {
11978        let output = run_output(r#"print(range(0, 10, 3))"#);
11979        assert_eq!(output, vec!["[0, 3, 6, 9]"]);
11980    }
11981
11982    #[test]
11983    fn test_int_bool() {
11984        let output = run_output(
11985            r#"print(int(true))
11986print(int(false))"#,
11987        );
11988        assert_eq!(output, vec!["1", "0"]);
11989    }
11990
11991    #[test]
11992    fn test_float_bool() {
11993        let output = run_output(
11994            r#"print(float(true))
11995print(float(false))"#,
11996        );
11997        assert_eq!(output, vec!["1.0", "0.0"]);
11998    }
11999
12000    #[test]
12001    fn test_integration_json_file_roundtrip() {
12002        let output = run_output(
12003            r#"let data = map_from("name", "test", "count", 42)
12004let json_str = json_stringify(data)
12005write_file("/tmp/tl_json_roundtrip.json", json_str)
12006let content = read_file("/tmp/tl_json_roundtrip.json")
12007let parsed = json_parse(content)
12008print(parsed["name"])
12009print(parsed["count"])"#,
12010        );
12011        assert_eq!(output, vec!["test", "42"]);
12012    }
12013
12014    #[test]
12015    fn test_integration_regex_on_file() {
12016        let output = run_output(
12017            r#"write_file("/tmp/tl_regex_test.txt", "Error: code 404\nInfo: ok\nError: code 500")
12018let content = read_file("/tmp/tl_regex_test.txt")
12019let errors = regex_find("Error: code \\d+", content)
12020print(len(errors))"#,
12021        );
12022        assert_eq!(output, vec!["2"]);
12023    }
12024
12025    #[test]
12026    fn test_integration_list_transform() {
12027        let output = run_output(
12028            r#"let data = [5, 3, 8, 1, 9, 2]
12029let result = data.sort().slice(0, 3)
12030print(result)"#,
12031        );
12032        assert_eq!(output, vec!["[1, 2, 3]"]);
12033    }
12034
12035    #[test]
12036    fn test_integration_map_values() {
12037        let output = run_output(
12038            r#"let m = map_from("a", 1, "b", 2, "c", 3)
12039let vals = m.values()
12040print(sum(vals))"#,
12041        );
12042        assert_eq!(output, vec!["6"]);
12043    }
12044
12045    // ── Phase 7: Concurrency tests ──
12046
12047    #[test]
12048    fn test_interp_spawn_await_basic() {
12049        let output = run_output(
12050            r#"fn worker() { 42 }
12051let t = spawn(worker)
12052let result = await t
12053print(result)"#,
12054        );
12055        assert_eq!(output, vec!["42"]);
12056    }
12057
12058    #[test]
12059    fn test_interp_spawn_closure_with_capture() {
12060        let output = run_output(
12061            r#"let x = 10
12062fn f() { x + 5 }
12063let t = spawn(f)
12064print(await t)"#,
12065        );
12066        assert_eq!(output, vec!["15"]);
12067    }
12068
12069    #[test]
12070    fn test_interp_sleep() {
12071        let output = run_output(
12072            r#"sleep(10)
12073print("done")"#,
12074        );
12075        assert_eq!(output, vec!["done"]);
12076    }
12077
12078    #[test]
12079    fn test_interp_await_non_task() {
12080        let output = run_output(r#"print(await 42)"#);
12081        assert_eq!(output, vec!["42"]);
12082    }
12083
12084    #[test]
12085    fn test_interp_channel_basic() {
12086        let output = run_output(
12087            r#"let ch = channel()
12088send(ch, 42)
12089let val = recv(ch)
12090print(val)"#,
12091        );
12092        assert_eq!(output, vec!["42"]);
12093    }
12094
12095    #[test]
12096    fn test_interp_channel_between_tasks() {
12097        let output = run_output(
12098            r#"let ch = channel()
12099fn producer() { send(ch, 100) }
12100let t = spawn(producer)
12101let val = recv(ch)
12102await t
12103print(val)"#,
12104        );
12105        assert_eq!(output, vec!["100"]);
12106    }
12107
12108    #[test]
12109    fn test_interp_try_recv_empty() {
12110        let output = run_output(
12111            r#"let ch = channel()
12112let val = try_recv(ch)
12113print(val)"#,
12114        );
12115        assert_eq!(output, vec!["none"]);
12116    }
12117
12118    #[test]
12119    fn test_interp_channel_multiple_values() {
12120        let output = run_output(
12121            r#"let ch = channel()
12122send(ch, 1)
12123send(ch, 2)
12124send(ch, 3)
12125print(recv(ch))
12126print(recv(ch))
12127print(recv(ch))"#,
12128        );
12129        assert_eq!(output, vec!["1", "2", "3"]);
12130    }
12131
12132    #[test]
12133    fn test_interp_channel_producer_consumer() {
12134        let output = run_output(
12135            r#"let ch = channel()
12136fn producer() {
12137    send(ch, 10)
12138    send(ch, 20)
12139    send(ch, 30)
12140}
12141let t = spawn(producer)
12142let a = recv(ch)
12143let b = recv(ch)
12144let c = recv(ch)
12145await t
12146print(a + b + c)"#,
12147        );
12148        assert_eq!(output, vec!["60"]);
12149    }
12150
12151    #[test]
12152    fn test_interp_await_all() {
12153        let output = run_output(
12154            r#"fn w1() { 10 }
12155fn w2() { 20 }
12156fn w3() { 30 }
12157let t1 = spawn(w1)
12158let t2 = spawn(w2)
12159let t3 = spawn(w3)
12160let results = await_all([t1, t2, t3])
12161print(sum(results))"#,
12162        );
12163        assert_eq!(output, vec!["60"]);
12164    }
12165
12166    #[test]
12167    fn test_interp_pmap_basic() {
12168        let output = run_output(
12169            r#"fn double(x) { x * 2 }
12170let results = pmap([1, 2, 3], double)
12171print(results)"#,
12172        );
12173        assert_eq!(output, vec!["[2, 4, 6]"]);
12174    }
12175
12176    #[test]
12177    fn test_interp_pmap_order() {
12178        let output = run_output(
12179            r#"fn inc(x) { x + 1 }
12180let results = pmap([10, 20, 30], inc)
12181print(results)"#,
12182        );
12183        assert_eq!(output, vec!["[11, 21, 31]"]);
12184    }
12185
12186    #[test]
12187    fn test_interp_timeout_success() {
12188        let output = run_output(
12189            r#"fn worker() { 42 }
12190let t = spawn(worker)
12191let result = timeout(t, 5000)
12192print(result)"#,
12193        );
12194        assert_eq!(output, vec!["42"]);
12195    }
12196
12197    #[test]
12198    fn test_interp_timeout_failure() {
12199        let output = run_output(
12200            r#"fn slow() { sleep(10000) }
12201let t = spawn(slow)
12202let result = "ok"
12203try {
12204    result = timeout(t, 50)
12205} catch e {
12206    result = e
12207}
12208print(result)"#,
12209        );
12210        assert_eq!(output, vec!["Task timed out"]);
12211    }
12212
12213    #[test]
12214    fn test_interp_spawn_error_propagation() {
12215        let output = run_output(
12216            r#"fn bad() { throw "bad thing" }
12217let result = "ok"
12218try {
12219    let t = spawn(bad)
12220    result = await t
12221} catch e {
12222    result = e
12223}
12224print(result)"#,
12225        );
12226        assert_eq!(output, vec!["bad thing"]);
12227    }
12228
12229    #[test]
12230    fn test_interp_spawn_multiple_collect() {
12231        let output = run_output(
12232            r#"fn w1() { 1 }
12233fn w2() { 2 }
12234fn w3() { 3 }
12235let t1 = spawn(w1)
12236let t2 = spawn(w2)
12237let t3 = spawn(w3)
12238let a = await t1
12239let b = await t2
12240let c = await t3
12241print(a + b + c)"#,
12242        );
12243        assert_eq!(output, vec!["6"]);
12244    }
12245
12246    #[test]
12247    fn test_interp_type_of_task_channel() {
12248        let output = run_output(
12249            r#"fn worker() { 1 }
12250let t = spawn(worker)
12251let ch = channel()
12252print(type_of(t))
12253print(type_of(ch))
12254await t"#,
12255        );
12256        assert_eq!(output, vec!["task", "channel"]);
12257    }
12258
12259    #[test]
12260    fn test_interp_producer_consumer_pipeline() {
12261        let output = run_output(
12262            r#"let ch = channel()
12263fn producer() {
12264    let mut i = 0
12265    while i < 5 {
12266        send(ch, i * 10)
12267        i = i + 1
12268    }
12269}
12270let t = spawn(producer)
12271let mut total = 0
12272let mut count = 0
12273while count < 5 {
12274    total = total + recv(ch)
12275    count = count + 1
12276}
12277await t
12278print(total)"#,
12279        );
12280        assert_eq!(output, vec!["100"]);
12281    }
12282
12283    // ── Phase 8: Generators & Iterators ──────────────────────
12284
12285    #[test]
12286    fn test_interp_basic_generator() {
12287        let output = run_output(
12288            r#"fn gen() {
12289    yield 1
12290    yield 2
12291    yield 3
12292}
12293let g = gen()
12294print(next(g))
12295print(next(g))
12296print(next(g))
12297print(next(g))"#,
12298        );
12299        assert_eq!(output, vec!["1", "2", "3", "none"]);
12300    }
12301
12302    #[test]
12303    fn test_interp_generator_exhaustion() {
12304        let output = run_output(
12305            r#"fn gen() { yield 42 }
12306let g = gen()
12307print(next(g))
12308print(next(g))
12309print(next(g))"#,
12310        );
12311        assert_eq!(output, vec!["42", "none", "none"]);
12312    }
12313
12314    #[test]
12315    fn test_interp_generator_with_loop() {
12316        let output = run_output(
12317            r#"fn counter() {
12318    let mut i = 0
12319    while i < 5 {
12320        yield i
12321        i = i + 1
12322    }
12323}
12324print(gen_collect(counter()))"#,
12325        );
12326        assert_eq!(output, vec!["[0, 1, 2, 3, 4]"]);
12327    }
12328
12329    #[test]
12330    fn test_interp_generator_with_args() {
12331        let output = run_output(
12332            r#"fn range_gen(start, end) {
12333    let mut i = start
12334    while i < end {
12335        yield i
12336        i = i + 1
12337    }
12338}
12339let g = range_gen(3, 7)
12340print(next(g))
12341print(next(g))
12342print(next(g))
12343print(next(g))
12344print(next(g))"#,
12345        );
12346        assert_eq!(output, vec!["3", "4", "5", "6", "none"]);
12347    }
12348
12349    #[test]
12350    fn test_interp_generator_yield_none() {
12351        let output = run_output(
12352            r#"fn gen() {
12353    yield
12354    yield 5
12355}
12356let g = gen()
12357print(next(g))
12358print(next(g))
12359print(next(g))"#,
12360        );
12361        assert_eq!(output, vec!["none", "5", "none"]);
12362    }
12363
12364    #[test]
12365    fn test_interp_is_generator() {
12366        let output = run_output(
12367            r#"fn gen() { yield 1 }
12368let g = gen()
12369print(is_generator(g))
12370print(is_generator(42))
12371print(is_generator(none))"#,
12372        );
12373        assert_eq!(output, vec!["true", "false", "false"]);
12374    }
12375
12376    #[test]
12377    fn test_interp_multiple_generators() {
12378        let output = run_output(
12379            r#"fn gen() {
12380    yield 1
12381    yield 2
12382}
12383let g1 = gen()
12384let g2 = gen()
12385print(next(g1))
12386print(next(g2))
12387print(next(g1))
12388print(next(g2))"#,
12389        );
12390        assert_eq!(output, vec!["1", "1", "2", "2"]);
12391    }
12392
12393    #[test]
12394    fn test_interp_for_over_generator() {
12395        let output = run_output(
12396            r#"fn gen() {
12397    yield 10
12398    yield 20
12399    yield 30
12400}
12401let mut sum = 0
12402for x in gen() {
12403    sum = sum + x
12404}
12405print(sum)"#,
12406        );
12407        assert_eq!(output, vec!["60"]);
12408    }
12409
12410    #[test]
12411    fn test_interp_iter_builtin() {
12412        let output = run_output(
12413            r#"let g = iter([10, 20, 30])
12414print(next(g))
12415print(next(g))
12416print(next(g))
12417print(next(g))"#,
12418        );
12419        assert_eq!(output, vec!["10", "20", "30", "none"]);
12420    }
12421
12422    #[test]
12423    fn test_interp_take_builtin() {
12424        let output = run_output(
12425            r#"fn naturals() {
12426    let mut n = 0
12427    while true {
12428        yield n
12429        n = n + 1
12430    }
12431}
12432print(gen_collect(take(naturals(), 5)))"#,
12433        );
12434        assert_eq!(output, vec!["[0, 1, 2, 3, 4]"]);
12435    }
12436
12437    #[test]
12438    fn test_interp_skip_builtin() {
12439        let output = run_output(
12440            r#"let g = skip(iter([1, 2, 3, 4, 5]), 3)
12441print(gen_collect(g))"#,
12442        );
12443        assert_eq!(output, vec!["[4, 5]"]);
12444    }
12445
12446    #[test]
12447    fn test_interp_gen_collect() {
12448        let output = run_output(
12449            r#"fn gen() {
12450    yield 1
12451    yield 2
12452    yield 3
12453}
12454print(gen_collect(gen()))"#,
12455        );
12456        assert_eq!(output, vec!["[1, 2, 3]"]);
12457    }
12458
12459    #[test]
12460    fn test_interp_gen_map() {
12461        let output = run_output(
12462            r#"let g = gen_map(iter([1, 2, 3]), (x) => x * 10)
12463print(gen_collect(g))"#,
12464        );
12465        assert_eq!(output, vec!["[10, 20, 30]"]);
12466    }
12467
12468    #[test]
12469    fn test_interp_gen_filter() {
12470        let output = run_output(
12471            r#"let g = gen_filter(iter([1, 2, 3, 4, 5, 6]), (x) => x % 2 == 0)
12472print(gen_collect(g))"#,
12473        );
12474        assert_eq!(output, vec!["[2, 4, 6]"]);
12475    }
12476
12477    #[test]
12478    fn test_interp_chain() {
12479        let output = run_output(
12480            r#"let g = chain(iter([1, 2]), iter([3, 4]))
12481print(gen_collect(g))"#,
12482        );
12483        assert_eq!(output, vec!["[1, 2, 3, 4]"]);
12484    }
12485
12486    #[test]
12487    fn test_interp_gen_zip() {
12488        let output = run_output(
12489            r#"let g = gen_zip(iter([1, 2, 3]), iter([10, 20]))
12490print(gen_collect(g))"#,
12491        );
12492        assert_eq!(output, vec!["[[1, 10], [2, 20]]"]);
12493    }
12494
12495    #[test]
12496    fn test_interp_gen_enumerate() {
12497        let output = run_output(
12498            r#"let g = gen_enumerate(iter([10, 20, 30]))
12499print(gen_collect(g))"#,
12500        );
12501        assert_eq!(output, vec!["[[0, 10], [1, 20], [2, 30]]"]);
12502    }
12503
12504    #[test]
12505    fn test_interp_combinator_chaining() {
12506        let output = run_output(
12507            r#"fn naturals() {
12508    let mut n = 0
12509    while true {
12510        yield n
12511        n = n + 1
12512    }
12513}
12514let result = gen_collect(gen_map(gen_filter(take(naturals(), 10), (x) => x % 2 == 0), (x) => x * x))
12515print(result)"#,
12516        );
12517        assert_eq!(output, vec!["[0, 4, 16, 36, 64]"]);
12518    }
12519
12520    #[test]
12521    fn test_interp_for_over_take() {
12522        let output = run_output(
12523            r#"fn naturals() {
12524    let mut n = 0
12525    while true {
12526        yield n
12527        n = n + 1
12528    }
12529}
12530let mut sum = 0
12531for x in take(naturals(), 5) {
12532    sum = sum + x
12533}
12534print(sum)"#,
12535        );
12536        assert_eq!(output, vec!["10"]);
12537    }
12538
12539    #[test]
12540    fn test_interp_fibonacci_generator() {
12541        let output = run_output(
12542            r#"fn fib() {
12543    let mut a = 0
12544    let mut b = 1
12545    while true {
12546        yield a
12547        let tmp = a + b
12548        a = b
12549        b = tmp
12550    }
12551}
12552print(gen_collect(take(fib(), 8)))"#,
12553        );
12554        assert_eq!(output, vec!["[0, 1, 1, 2, 3, 5, 8, 13]"]);
12555    }
12556
12557    #[test]
12558    fn test_interp_generator_method_syntax() {
12559        let output = run_output(
12560            r#"fn gen() {
12561    yield 1
12562    yield 2
12563    yield 3
12564}
12565let g = gen()
12566print(g.next())
12567print(g.next())
12568print(g.collect())"#,
12569        );
12570        assert_eq!(output, vec!["1", "2", "[3]"]);
12571    }
12572
12573    // ── Phase 10: Result/Option + ? operator tests ──
12574
12575    #[test]
12576    fn test_interp_ok_err_builtins() {
12577        let output = run_output("let r = Ok(42)\nprint(r)");
12578        assert_eq!(output, vec!["Result::Ok(42)"]);
12579
12580        let output = run_output("let r = Err(\"fail\")\nprint(r)");
12581        assert_eq!(output, vec!["Result::Err(fail)"]);
12582    }
12583
12584    #[test]
12585    fn test_interp_is_ok_is_err() {
12586        let output = run_output("print(is_ok(Ok(42)))");
12587        assert_eq!(output, vec!["true"]);
12588        let output = run_output("print(is_err(Err(\"fail\")))");
12589        assert_eq!(output, vec!["true"]);
12590    }
12591
12592    #[test]
12593    fn test_interp_unwrap_ok() {
12594        let output = run_output("print(unwrap(Ok(42)))");
12595        assert_eq!(output, vec!["42"]);
12596    }
12597
12598    #[test]
12599    fn test_interp_unwrap_err_panics() {
12600        let result = run("unwrap(Err(\"fail\"))");
12601        assert!(result.is_err());
12602    }
12603
12604    #[test]
12605    fn test_interp_try_on_ok() {
12606        let output = run_output(
12607            r#"fn get_val() { Ok(42) }
12608fn process() { let v = get_val()? + 1
12609Ok(v) }
12610print(process())"#,
12611        );
12612        assert_eq!(output, vec!["Result::Ok(43)"]);
12613    }
12614
12615    #[test]
12616    fn test_interp_try_on_err_propagates() {
12617        let output = run_output(
12618            r#"fn failing() { Err("oops") }
12619fn process() { let v = failing()?
12620Ok(v) }
12621print(process())"#,
12622        );
12623        assert_eq!(output, vec!["Result::Err(oops)"]);
12624    }
12625
12626    #[test]
12627    fn test_interp_try_on_none_propagates() {
12628        let output = run_output(
12629            r#"fn get_none() { none }
12630fn process() { let v = get_none()?
1263142 }
12632print(process())"#,
12633        );
12634        assert_eq!(output, vec!["none"]);
12635    }
12636
12637    #[test]
12638    fn test_interp_result_match() {
12639        let output = run_output(
12640            r#"let r = Ok(42)
12641match r {
12642    Result::Ok(v) => print(v),
12643    Result::Err(e) => print(e)
12644}"#,
12645        );
12646        assert_eq!(output, vec!["42"]);
12647    }
12648
12649    // ── Set tests ──
12650
12651    #[test]
12652    fn test_interp_set_from_dedup() {
12653        let output = run_output(
12654            r#"let s = set_from([1, 2, 3, 2, 1])
12655print(len(s))
12656print(type_of(s))"#,
12657        );
12658        assert_eq!(output, vec!["3", "set"]);
12659    }
12660
12661    #[test]
12662    fn test_interp_set_add() {
12663        let output = run_output(
12664            r#"let s = set_from([1, 2])
12665let s2 = set_add(s, 3)
12666let s3 = set_add(s2, 2)
12667print(len(s2))
12668print(len(s3))"#,
12669        );
12670        assert_eq!(output, vec!["3", "3"]);
12671    }
12672
12673    #[test]
12674    fn test_interp_set_remove() {
12675        let output = run_output(
12676            r#"let s = set_from([1, 2, 3])
12677let s2 = set_remove(s, 2)
12678print(len(s2))
12679print(set_contains(s2, 2))"#,
12680        );
12681        assert_eq!(output, vec!["2", "false"]);
12682    }
12683
12684    #[test]
12685    fn test_interp_set_contains() {
12686        let output = run_output(
12687            r#"let s = set_from([1, 2, 3])
12688print(set_contains(s, 2))
12689print(set_contains(s, 5))"#,
12690        );
12691        assert_eq!(output, vec!["true", "false"]);
12692    }
12693
12694    #[test]
12695    fn test_interp_set_union() {
12696        let output = run_output(
12697            r#"let a = set_from([1, 2, 3])
12698let b = set_from([3, 4, 5])
12699let c = set_union(a, b)
12700print(len(c))"#,
12701        );
12702        assert_eq!(output, vec!["5"]);
12703    }
12704
12705    #[test]
12706    fn test_interp_set_intersection() {
12707        let output = run_output(
12708            r#"let a = set_from([1, 2, 3])
12709let b = set_from([2, 3, 4])
12710let c = set_intersection(a, b)
12711print(len(c))"#,
12712        );
12713        assert_eq!(output, vec!["2"]);
12714    }
12715
12716    #[test]
12717    fn test_interp_set_difference() {
12718        let output = run_output(
12719            r#"let a = set_from([1, 2, 3])
12720let b = set_from([2, 3, 4])
12721let c = set_difference(a, b)
12722print(len(c))"#,
12723        );
12724        assert_eq!(output, vec!["1"]);
12725    }
12726
12727    #[test]
12728    fn test_interp_set_for_loop() {
12729        let output = run_output(
12730            r#"let s = set_from([10, 20, 30])
12731let total = 0
12732for item in s {
12733    total = total + item
12734}
12735print(total)"#,
12736        );
12737        assert_eq!(output, vec!["60"]);
12738    }
12739
12740    #[test]
12741    fn test_interp_set_to_list() {
12742        let output = run_output(
12743            r#"let s = set_from([3, 1, 2])
12744let lst = s.to_list()
12745print(type_of(lst))
12746print(len(lst))"#,
12747        );
12748        assert_eq!(output, vec!["list", "3"]);
12749    }
12750
12751    #[test]
12752    fn test_interp_set_method_contains() {
12753        let output = run_output(
12754            r#"let s = set_from([1, 2, 3])
12755print(s.contains(2))
12756print(s.contains(5))"#,
12757        );
12758        assert_eq!(output, vec!["true", "false"]);
12759    }
12760
12761    #[test]
12762    fn test_interp_set_method_add_remove() {
12763        let output = run_output(
12764            r#"let s = set_from([1, 2])
12765let s2 = s.add(3)
12766print(s2.len())
12767let s3 = s2.remove(1)
12768print(s3.len())"#,
12769        );
12770        assert_eq!(output, vec!["3", "2"]);
12771    }
12772
12773    #[test]
12774    fn test_interp_set_method_union_intersection_difference() {
12775        let output = run_output(
12776            r#"let a = set_from([1, 2, 3])
12777let b = set_from([2, 3, 4])
12778print(a.union(b).len())
12779print(a.intersection(b).len())
12780print(a.difference(b).len())"#,
12781        );
12782        assert_eq!(output, vec!["4", "2", "1"]);
12783    }
12784
12785    #[test]
12786    fn test_interp_set_empty() {
12787        let output = run_output(
12788            r#"let s = set_from([])
12789print(len(s))
12790let s2 = s.add(1)
12791print(len(s2))"#,
12792        );
12793        assert_eq!(output, vec!["0", "1"]);
12794    }
12795
12796    #[test]
12797    fn test_interp_set_string_values() {
12798        let output = run_output(
12799            r#"let s = set_from(["a", "b", "a", "c"])
12800print(len(s))
12801print(s.contains("b"))"#,
12802        );
12803        assert_eq!(output, vec!["3", "true"]);
12804    }
12805
12806    // ── Phase 11: Module System Interpreter Tests ──
12807
12808    #[test]
12809    fn test_interp_use_single_file() {
12810        let dir = tempfile::tempdir().unwrap();
12811        std::fs::write(dir.path().join("math.tl"), "fn add(a, b) { a + b }").unwrap();
12812
12813        let main_path = dir.path().join("main.tl");
12814        let main_src = "use math\nprint(add(1, 2))";
12815        std::fs::write(&main_path, main_src).unwrap();
12816
12817        let program = tl_parser::parse(main_src).unwrap();
12818        let mut interp = Interpreter::new();
12819        interp.file_path = Some(main_path.to_string_lossy().to_string());
12820        interp.execute(&program).unwrap();
12821        assert_eq!(interp.output, vec!["3"]);
12822    }
12823
12824    #[test]
12825    fn test_interp_use_wildcard() {
12826        let dir = tempfile::tempdir().unwrap();
12827        std::fs::write(
12828            dir.path().join("helpers.tl"),
12829            "fn greet() { \"hello\" }\nfn farewell() { \"bye\" }",
12830        )
12831        .unwrap();
12832
12833        let main_path = dir.path().join("main.tl");
12834        let main_src = "use helpers.*\nprint(greet())\nprint(farewell())";
12835        std::fs::write(&main_path, main_src).unwrap();
12836
12837        let program = tl_parser::parse(main_src).unwrap();
12838        let mut interp = Interpreter::new();
12839        interp.file_path = Some(main_path.to_string_lossy().to_string());
12840        interp.execute(&program).unwrap();
12841        assert_eq!(interp.output, vec!["hello", "bye"]);
12842    }
12843
12844    #[test]
12845    fn test_interp_use_aliased() {
12846        let dir = tempfile::tempdir().unwrap();
12847        std::fs::write(dir.path().join("mylib.tl"), "fn compute() { 42 }").unwrap();
12848
12849        let main_path = dir.path().join("main.tl");
12850        let main_src = "use mylib as m\nprint(m.compute())";
12851        std::fs::write(&main_path, main_src).unwrap();
12852
12853        let program = tl_parser::parse(main_src).unwrap();
12854        let mut interp = Interpreter::new();
12855        interp.file_path = Some(main_path.to_string_lossy().to_string());
12856        interp.execute(&program).unwrap();
12857        assert_eq!(interp.output, vec!["42"]);
12858    }
12859
12860    #[test]
12861    fn test_interp_use_directory_module() {
12862        let dir = tempfile::tempdir().unwrap();
12863        std::fs::create_dir_all(dir.path().join("utils")).unwrap();
12864        std::fs::write(dir.path().join("utils/mod.tl"), "fn helper() { 99 }").unwrap();
12865
12866        let main_path = dir.path().join("main.tl");
12867        let main_src = "use utils\nprint(helper())";
12868        std::fs::write(&main_path, main_src).unwrap();
12869
12870        let program = tl_parser::parse(main_src).unwrap();
12871        let mut interp = Interpreter::new();
12872        interp.file_path = Some(main_path.to_string_lossy().to_string());
12873        interp.execute(&program).unwrap();
12874        assert_eq!(interp.output, vec!["99"]);
12875    }
12876
12877    #[test]
12878    fn test_interp_use_nested_path() {
12879        let dir = tempfile::tempdir().unwrap();
12880        std::fs::create_dir_all(dir.path().join("data")).unwrap();
12881        std::fs::write(
12882            dir.path().join("data/transforms.tl"),
12883            "fn clean(x) { x + 1 }",
12884        )
12885        .unwrap();
12886
12887        let main_path = dir.path().join("main.tl");
12888        let main_src = "use data.transforms\nprint(clean(41))";
12889        std::fs::write(&main_path, main_src).unwrap();
12890
12891        let program = tl_parser::parse(main_src).unwrap();
12892        let mut interp = Interpreter::new();
12893        interp.file_path = Some(main_path.to_string_lossy().to_string());
12894        interp.execute(&program).unwrap();
12895        assert_eq!(interp.output, vec!["42"]);
12896    }
12897
12898    #[test]
12899    fn test_interp_pub_fn() {
12900        let output = run_output("pub fn add(a, b) { a + b }\nprint(add(1, 2))");
12901        assert_eq!(output, vec!["3"]);
12902    }
12903
12904    #[test]
12905    fn test_interp_module_caching() {
12906        let dir = tempfile::tempdir().unwrap();
12907        std::fs::write(dir.path().join("cached.tl"), "let X = 42").unwrap();
12908
12909        let main_path = dir.path().join("main.tl");
12910        let main_src = "use cached\nuse cached\nprint(X)";
12911        std::fs::write(&main_path, main_src).unwrap();
12912
12913        let program = tl_parser::parse(main_src).unwrap();
12914        let mut interp = Interpreter::new();
12915        interp.file_path = Some(main_path.to_string_lossy().to_string());
12916        interp.execute(&program).unwrap();
12917        assert_eq!(interp.output, vec!["42"]);
12918    }
12919
12920    #[test]
12921    fn test_interp_circular_import() {
12922        let dir = tempfile::tempdir().unwrap();
12923        let a_path = dir.path().join("a.tl");
12924        let b_path = dir.path().join("b.tl");
12925        std::fs::write(&a_path, &format!("import \"{}\"", b_path.to_string_lossy())).unwrap();
12926        std::fs::write(&b_path, &format!("import \"{}\"", a_path.to_string_lossy())).unwrap();
12927
12928        let source = std::fs::read_to_string(&a_path).unwrap();
12929        let program = tl_parser::parse(&source).unwrap();
12930        let mut interp = Interpreter::new();
12931        interp.file_path = Some(a_path.to_string_lossy().to_string());
12932        let result = interp.execute(&program);
12933        assert!(result.is_err());
12934        assert!(format!("{:?}", result).contains("Circular"));
12935    }
12936
12937    #[test]
12938    fn test_interp_existing_import_still_works() {
12939        let dir = tempfile::tempdir().unwrap();
12940        let lib_path = dir.path().join("lib.tl");
12941        std::fs::write(&lib_path, "fn imported_fn() { 123 }").unwrap();
12942
12943        let main_src = format!(
12944            "import \"{}\"\nprint(imported_fn())",
12945            lib_path.to_string_lossy()
12946        );
12947        let program = tl_parser::parse(&main_src).unwrap();
12948        let mut interp = Interpreter::new();
12949        interp.execute(&program).unwrap();
12950        assert_eq!(interp.output, vec!["123"]);
12951    }
12952
12953    #[test]
12954    fn test_interp_use_group() {
12955        let dir = tempfile::tempdir().unwrap();
12956        std::fs::write(
12957            dir.path().join("lib.tl"),
12958            "fn foo() { 1 }\nfn bar() { 2 }\nfn baz() { 3 }",
12959        )
12960        .unwrap();
12961
12962        let main_path = dir.path().join("main.tl");
12963        let main_src = "use lib.{foo, bar}\nprint(foo())\nprint(bar())";
12964        std::fs::write(&main_path, main_src).unwrap();
12965
12966        let program = tl_parser::parse(main_src).unwrap();
12967        let mut interp = Interpreter::new();
12968        interp.file_path = Some(main_path.to_string_lossy().to_string());
12969        interp.execute(&program).unwrap();
12970        assert_eq!(interp.output, vec!["1", "2"]);
12971    }
12972
12973    // ── Phase 12: Generics & Traits (Interpreter) ──────────
12974
12975    #[test]
12976    fn test_interp_generic_fn() {
12977        let output = run_output("fn identity<T>(x: T) -> T { x }\nprint(identity(42))");
12978        assert_eq!(output, vec!["42"]);
12979    }
12980
12981    #[test]
12982    fn test_interp_generic_fn_string() {
12983        let output = run_output("fn identity<T>(x: T) -> T { x }\nprint(identity(\"hello\"))");
12984        assert_eq!(output, vec!["hello"]);
12985    }
12986
12987    #[test]
12988    fn test_interp_generic_struct() {
12989        let output = run_output(
12990            "struct Pair<A, B> { first: A, second: B }\nlet p = Pair { first: 1, second: \"hi\" }\nprint(p.first)\nprint(p.second)",
12991        );
12992        assert_eq!(output, vec!["1", "hi"]);
12993    }
12994
12995    #[test]
12996    fn test_interp_trait_def_noop() {
12997        let output = run_output("trait Display { fn show(self) -> string }\nprint(\"ok\")");
12998        assert_eq!(output, vec!["ok"]);
12999    }
13000
13001    #[test]
13002    fn test_interp_trait_impl_methods() {
13003        let output = run_output(
13004            "struct Point { x: int, y: int }\nimpl Display for Point { fn show(self) -> string { \"point\" } }\nlet p = Point { x: 1, y: 2 }\nprint(p.show())",
13005        );
13006        assert_eq!(output, vec!["point"]);
13007    }
13008
13009    #[test]
13010    fn test_interp_generic_enum() {
13011        // Generic enum declaration works — type params are erased at runtime
13012        let output = run_output(
13013            "enum MyOpt<T> { Some(T), Nothing }\nlet x = MyOpt::Some(42)\nlet y = MyOpt::Nothing\nprint(type_of(x))\nprint(type_of(y))",
13014        );
13015        assert_eq!(output, vec!["enum", "enum"]);
13016    }
13017
13018    #[test]
13019    fn test_interp_where_clause_runtime() {
13020        let output =
13021            run_output("fn compare<T>(x: T) where T: Comparable { x }\nprint(compare(10))");
13022        assert_eq!(output, vec!["10"]);
13023    }
13024
13025    #[test]
13026    fn test_interp_trait_impl_self_method() {
13027        let output = run_output(
13028            "struct Counter { value: int }\nimpl Incrementable for Counter { fn inc(self) { self.value + 1 } }\nlet c = Counter { value: 5 }\nprint(c.inc())",
13029        );
13030        assert_eq!(output, vec!["6"]);
13031    }
13032
13033    // ── Phase 12: Integration tests ──────────────────────────
13034
13035    #[test]
13036    fn test_interp_generic_fn_with_type_inference() {
13037        let output = run_output(
13038            "fn first<T>(xs: list<T>) -> T { xs[0] }\nprint(first([1, 2, 3]))\nprint(first([\"a\", \"b\"]))",
13039        );
13040        assert_eq!(output, vec!["1", "a"]);
13041    }
13042
13043    #[test]
13044    fn test_interp_generic_struct_with_methods() {
13045        let output = run_output(
13046            "struct Box<T> { val: T }\nimpl Box { fn get(self) { self.val } }\nlet b = Box { val: 42 }\nprint(b.get())",
13047        );
13048        assert_eq!(output, vec!["42"]);
13049    }
13050
13051    #[test]
13052    fn test_interp_trait_def_impl_call() {
13053        let output = run_output(
13054            "trait Greetable { fn greet(self) -> string }\nstruct Person { name: string }\nimpl Greetable for Person { fn greet(self) -> string { self.name } }\nlet p = Person { name: \"Alice\" }\nprint(p.greet())",
13055        );
13056        assert_eq!(output, vec!["Alice"]);
13057    }
13058
13059    #[test]
13060    fn test_interp_multiple_generic_params() {
13061        let output = run_output(
13062            "fn pair<A, B>(a: A, b: B) { [a, b] }\nlet p = pair(1, \"two\")\nprint(len(p))",
13063        );
13064        assert_eq!(output, vec!["2"]);
13065    }
13066
13067    #[test]
13068    fn test_interp_backward_compat_non_generic() {
13069        let output = run_output(
13070            "fn add(a, b) { a + b }\nstruct Point { x: int, y: int }\nimpl Point { fn sum(self) { self.x + self.y } }\nlet p = Point { x: 3, y: 4 }\nprint(add(1, 2))\nprint(p.sum())",
13071        );
13072        assert_eq!(output, vec!["3", "7"]);
13073    }
13074
13075    // ── Phase 16: Package import resolution tests ──
13076
13077    #[test]
13078    fn test_interp_package_import() {
13079        let tmp = tempfile::tempdir().unwrap();
13080        let pkg_dir = tmp.path().join("mylib");
13081        std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
13082        std::fs::write(
13083            pkg_dir.join("src/lib.tl"),
13084            "pub fn greet() { print(\"hello from pkg\") }",
13085        )
13086        .unwrap();
13087        std::fs::write(
13088            pkg_dir.join("tl.toml"),
13089            "[project]\nname = \"mylib\"\nversion = \"1.0.0\"\n",
13090        )
13091        .unwrap();
13092
13093        let main_file = tmp.path().join("main.tl");
13094        std::fs::write(&main_file, "use mylib\ngreet()").unwrap();
13095
13096        let source = std::fs::read_to_string(&main_file).unwrap();
13097        let program = tl_parser::parse(&source).unwrap();
13098
13099        let mut interp = Interpreter::new();
13100        interp.file_path = Some(main_file.to_string_lossy().to_string());
13101        interp.package_roots.insert("mylib".into(), pkg_dir);
13102        interp.execute(&program).unwrap();
13103
13104        assert_eq!(interp.output, vec!["hello from pkg"]);
13105    }
13106
13107    #[test]
13108    fn test_interp_package_nested() {
13109        let tmp = tempfile::tempdir().unwrap();
13110        let pkg_dir = tmp.path().join("utils");
13111        std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
13112        std::fs::write(pkg_dir.join("src/math.tl"), "pub fn triple(x) { x * 3 }").unwrap();
13113        std::fs::write(
13114            pkg_dir.join("tl.toml"),
13115            "[project]\nname = \"utils\"\nversion = \"1.0.0\"\n",
13116        )
13117        .unwrap();
13118
13119        let main_file = tmp.path().join("main.tl");
13120        std::fs::write(&main_file, "use utils.math\nprint(triple(10))").unwrap();
13121
13122        let source = std::fs::read_to_string(&main_file).unwrap();
13123        let program = tl_parser::parse(&source).unwrap();
13124
13125        let mut interp = Interpreter::new();
13126        interp.file_path = Some(main_file.to_string_lossy().to_string());
13127        interp.package_roots.insert("utils".into(), pkg_dir);
13128        interp.execute(&program).unwrap();
13129
13130        assert_eq!(interp.output, vec!["30"]);
13131    }
13132
13133    #[test]
13134    fn test_interp_package_underscore_to_hyphen() {
13135        let tmp = tempfile::tempdir().unwrap();
13136        let pkg_dir = tmp.path().join("my-lib");
13137        std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
13138        std::fs::write(pkg_dir.join("src/lib.tl"), "pub fn val() { print(77) }").unwrap();
13139        std::fs::write(
13140            pkg_dir.join("tl.toml"),
13141            "[project]\nname = \"my-lib\"\nversion = \"1.0.0\"\n",
13142        )
13143        .unwrap();
13144
13145        let main_file = tmp.path().join("main.tl");
13146        std::fs::write(&main_file, "use my_lib\nval()").unwrap();
13147
13148        let source = std::fs::read_to_string(&main_file).unwrap();
13149        let program = tl_parser::parse(&source).unwrap();
13150
13151        let mut interp = Interpreter::new();
13152        interp.file_path = Some(main_file.to_string_lossy().to_string());
13153        interp.package_roots.insert("my-lib".into(), pkg_dir);
13154        interp.execute(&program).unwrap();
13155
13156        assert_eq!(interp.output, vec!["77"]);
13157    }
13158
13159    // ── Phase 17: Pattern Matching & Destructuring ──
13160
13161    #[test]
13162    fn test_interp_match_binding() {
13163        let output = run_output("let x = 42\nprint(match x { val => val + 1 })");
13164        assert_eq!(output, vec!["43"]);
13165    }
13166
13167    #[test]
13168    fn test_interp_match_guard() {
13169        let output = run_output(
13170            "let x = 5\nprint(match x { n if n > 0 => \"pos\", n if n < 0 => \"neg\", _ => \"zero\" })",
13171        );
13172        assert_eq!(output, vec!["pos"]);
13173    }
13174
13175    #[test]
13176    fn test_interp_match_guard_negative() {
13177        let output = run_output(
13178            "let x = -3\nprint(match x { n if n > 0 => \"pos\", n if n < 0 => \"neg\", _ => \"zero\" })",
13179        );
13180        assert_eq!(output, vec!["neg"]);
13181    }
13182
13183    #[test]
13184    fn test_interp_match_guard_zero() {
13185        let output = run_output(
13186            "let x = 0\nprint(match x { n if n > 0 => \"pos\", n if n < 0 => \"neg\", _ => \"zero\" })",
13187        );
13188        assert_eq!(output, vec!["zero"]);
13189    }
13190
13191    #[test]
13192    fn test_interp_match_enum_destructure() {
13193        let src = r#"
13194enum Shape { Circle(r), Rect(w, h) }
13195let s = Shape::Circle(5)
13196print(match s { Shape::Circle(r) => r * r, Shape::Rect(w, h) => w * h, _ => 0 })
13197"#;
13198        let output = run_output(src);
13199        assert_eq!(output, vec!["25"]);
13200    }
13201
13202    #[test]
13203    fn test_interp_match_enum_rect() {
13204        let src = r#"
13205enum Shape { Circle(r), Rect(w, h) }
13206let s = Shape::Rect(3, 4)
13207print(match s { Shape::Circle(r) => r * r, Shape::Rect(w, h) => w * h, _ => 0 })
13208"#;
13209        let output = run_output(src);
13210        assert_eq!(output, vec!["12"]);
13211    }
13212
13213    #[test]
13214    fn test_interp_match_or_pattern() {
13215        let output =
13216            run_output("let x = 2\nprint(match x { 1 or 2 or 3 => \"small\", _ => \"big\" })");
13217        assert_eq!(output, vec!["small"]);
13218    }
13219
13220    #[test]
13221    fn test_interp_match_or_pattern_no_match() {
13222        let output =
13223            run_output("let x = 10\nprint(match x { 1 or 2 or 3 => \"small\", _ => \"big\" })");
13224        assert_eq!(output, vec!["big"]);
13225    }
13226
13227    #[test]
13228    fn test_interp_match_struct_pattern() {
13229        let src = r#"
13230struct Point { x: int64, y: int64 }
13231let p = Point { x: 10, y: 20 }
13232print(match p { Point { x, y } => x + y, _ => 0 })
13233"#;
13234        let output = run_output(src);
13235        assert_eq!(output, vec!["30"]);
13236    }
13237
13238    #[test]
13239    fn test_interp_match_list_pattern() {
13240        let src = r#"
13241let lst = [10, 20, 30]
13242print(match lst { [a, b, c] => a + b + c, _ => 0 })
13243"#;
13244        let output = run_output(src);
13245        assert_eq!(output, vec!["60"]);
13246    }
13247
13248    #[test]
13249    fn test_interp_match_list_rest() {
13250        let src = r#"
13251let lst = [1, 2, 3, 4, 5]
13252print(match lst { [head, ...tail] => head, _ => 0 })
13253"#;
13254        let output = run_output(src);
13255        assert_eq!(output, vec!["1"]);
13256    }
13257
13258    #[test]
13259    fn test_interp_match_empty_list() {
13260        let src = r#"
13261let lst = []
13262print(match lst { [] => "empty", _ => "nonempty" })
13263"#;
13264        let output = run_output(src);
13265        assert_eq!(output, vec!["empty"]);
13266    }
13267
13268    #[test]
13269    fn test_interp_let_destructure_list() {
13270        let output = run_output("let [a, b, c] = [10, 20, 30]\nprint(a + b + c)");
13271        assert_eq!(output, vec!["60"]);
13272    }
13273
13274    #[test]
13275    fn test_interp_let_destructure_list_rest() {
13276        let output =
13277            run_output("let [head, ...tail] = [1, 2, 3, 4]\nprint(head)\nprint(len(tail))");
13278        assert_eq!(output, vec!["1", "3"]);
13279    }
13280
13281    #[test]
13282    fn test_interp_let_destructure_struct() {
13283        let src = r#"
13284struct Point { x: int64, y: int64 }
13285let p = Point { x: 5, y: 10 }
13286let Point { x, y } = p
13287print(x + y)
13288"#;
13289        let output = run_output(src);
13290        assert_eq!(output, vec!["15"]);
13291    }
13292
13293    #[test]
13294    fn test_interp_match_guard_enum() {
13295        let src = r#"
13296enum Result { Ok(v), Err(e) }
13297let r = Result::Ok(42)
13298print(match r { Result::Ok(v) if v > 100 => "big", Result::Ok(v) => v, Result::Err(e) => e, _ => "other" })
13299"#;
13300        let output = run_output(src);
13301        assert_eq!(output, vec!["42"]);
13302    }
13303
13304    #[test]
13305    fn test_interp_match_negative_literal() {
13306        let output =
13307            run_output("let x = -5\nprint(match x { -5 => \"neg five\", _ => \"other\" })");
13308        assert_eq!(output, vec!["neg five"]);
13309    }
13310
13311    #[test]
13312    fn test_interp_case_with_pattern() {
13313        let output = run_output(
13314            "let x = 15\nprint(case { x > 10 => \"big\", x > 0 => \"small\", _ => \"other\" })",
13315        );
13316        assert_eq!(output, vec!["big"]);
13317    }
13318
13319    // ── Phase 18: Closures & Lambdas Improvements ────────────────
13320
13321    #[test]
13322    fn test_interp_block_body_closure() {
13323        let output =
13324            run_output("let f = (x: int64) -> int64 { let y = x * 2\n y + 1 }\nprint(f(5))");
13325        assert_eq!(output, vec!["11"]);
13326    }
13327
13328    #[test]
13329    fn test_interp_block_body_closure_captured_var() {
13330        let output = run_output(
13331            "let offset = 10\nlet f = (x) -> int64 { let y = x + offset\n y }\nprint(f(5))",
13332        );
13333        assert_eq!(output, vec!["15"]);
13334    }
13335
13336    #[test]
13337    fn test_interp_block_body_closure_as_hof_arg() {
13338        let output = run_output(
13339            "let nums = [1, 2, 3]\nlet result = map(nums, (x) -> int64 { let doubled = x * 2\n doubled + 1 })\nprint(result)",
13340        );
13341        assert_eq!(output, vec!["[3, 5, 7]"]);
13342    }
13343
13344    #[test]
13345    fn test_interp_type_alias_noop() {
13346        let output = run_output(
13347            "type Mapper = fn(int64) -> int64\nlet f: Mapper = (x) => x * 2\nprint(f(5))",
13348        );
13349        assert_eq!(output, vec!["10"]);
13350    }
13351
13352    #[test]
13353    fn test_interp_type_alias_in_function_sig() {
13354        let output = run_output(
13355            "type Mapper = fn(int64) -> int64\nfn apply(f: Mapper, x: int64) -> int64 { f(x) }\nprint(apply((x) => x + 10, 5))",
13356        );
13357        assert_eq!(output, vec!["15"]);
13358    }
13359
13360    #[test]
13361    fn test_interp_shorthand_closure() {
13362        let output = run_output("let double = x => x * 2\nprint(double(5))");
13363        assert_eq!(output, vec!["10"]);
13364    }
13365
13366    #[test]
13367    fn test_interp_iife() {
13368        let output = run_output("let r = ((x) => x * 2)(5)\nprint(r)");
13369        assert_eq!(output, vec!["10"]);
13370    }
13371
13372    #[test]
13373    fn test_interp_closure_as_return_value() {
13374        let output = run_output(
13375            "fn make_adder(n) { (x) => x + n }\nlet add5 = make_adder(5)\nprint(add5(3))",
13376        );
13377        assert_eq!(output, vec!["8"]);
13378    }
13379
13380    #[test]
13381    fn test_interp_hof_apply() {
13382        let output = run_output("fn apply(f, x) { f(x) }\nprint(apply((x) => x + 10, 5))");
13383        assert_eq!(output, vec!["15"]);
13384    }
13385
13386    #[test]
13387    fn test_interp_nested_closures() {
13388        let output = run_output("let add = (a) => (b) => a + b\nlet add3 = add(3)\nprint(add3(4))");
13389        assert_eq!(output, vec!["7"]);
13390    }
13391
13392    #[test]
13393    fn test_interp_recursive_closure() {
13394        let output =
13395            run_output("fn fact(n) { if n <= 1 { 1 } else { n * fact(n - 1) } }\nprint(fact(5))");
13396        assert_eq!(output, vec!["120"]);
13397    }
13398
13399    #[test]
13400    fn test_interp_block_body_closure_with_return() {
13401        let output = run_output(
13402            "let classify = (x) -> string { if x > 0 { return \"positive\" }\n \"non-positive\" }\nprint(classify(5))\nprint(classify(-1))",
13403        );
13404        assert_eq!(output, vec!["positive", "non-positive"]);
13405    }
13406
13407    #[test]
13408    fn test_interp_shorthand_in_filter() {
13409        let output = run_output(
13410            "let nums = [1, 2, 3, 4, 5, 6]\nlet evens = filter(nums, x => x % 2 == 0)\nprint(evens)",
13411        );
13412        assert_eq!(output, vec!["[2, 4, 6]"]);
13413    }
13414
13415    #[test]
13416    fn test_interp_both_backends_expr() {
13417        let output = run_output("let f = (x) => x * 3 + 1\nprint(f(4))");
13418        assert_eq!(output, vec!["13"]);
13419    }
13420
13421    // Phase 21: Schema Evolution — interpreter tests
13422
13423    #[test]
13424    fn test_interp_versioned_schema_registration() {
13425        // Parse and execute a versioned schema definition
13426        let output = run_output(
13427            r#"
13428/// User schema
13429/// @version 1
13430schema User {
13431    id: int64
13432    name: string
13433}
13434print(schema_latest("User"))
13435"#,
13436        );
13437        assert_eq!(output, vec!["1"]);
13438    }
13439
13440    #[test]
13441    fn test_interp_schema_v1_v2_migration() {
13442        let output = run_output(
13443            r#"
13444/// @version 1
13445schema User {
13446    id: int64
13447    name: string
13448}
13449/// @version 2
13450schema UserV2 {
13451    id: int64
13452    name: string
13453    email: string
13454}
13455schema_register("User", 2, map_from("id", "int64", "name", "string", "email", "string"))
13456print(schema_latest("User"))
13457"#,
13458        );
13459        assert_eq!(output, vec!["2"]);
13460    }
13461
13462    #[test]
13463    fn test_interp_schema_latest() {
13464        let output = run_output(
13465            r#"
13466schema_register("Order", 1, map_from("id", "int64"))
13467schema_register("Order", 2, map_from("id", "int64", "total", "float64"))
13468schema_register("Order", 3, map_from("id", "int64", "total", "float64", "status", "string"))
13469print(schema_latest("Order"))
13470"#,
13471        );
13472        assert_eq!(output, vec!["3"]);
13473    }
13474
13475    #[test]
13476    fn test_interp_schema_history() {
13477        let output = run_output(
13478            r#"
13479schema_register("Event", 1, map_from("id", "int64"))
13480schema_register("Event", 2, map_from("id", "int64", "name", "string"))
13481print(schema_history("Event"))
13482"#,
13483        );
13484        assert_eq!(output, vec!["[1, 2]"]);
13485    }
13486
13487    #[test]
13488    fn test_interp_schema_check_backward_compat() {
13489        let output = run_output(
13490            r#"
13491schema_register("T", 1, map_from("id", "int64"))
13492schema_register("T", 2, map_from("id", "int64", "name", "string"))
13493let issues = schema_check("T", 1, 2, "backward")
13494print(len(issues))
13495"#,
13496        );
13497        // Adding a column is backward compatible
13498        assert_eq!(output, vec!["0"]);
13499    }
13500
13501    #[test]
13502    fn test_interp_migrate_add_column() {
13503        let output = run_output(
13504            r#"
13505/// @version 1
13506schema Product {
13507    id: int64
13508    name: string
13509}
13510migrate Product from 1 to 2 {
13511    add_column(price: float64, default: "0.0")
13512}
13513print(schema_latest("Product"))
13514let fields = schema_fields("Product", 2)
13515print(len(fields))
13516"#,
13517        );
13518        assert_eq!(output, vec!["2", "3"]);
13519    }
13520
13521    #[test]
13522    fn test_interp_migrate_rename_column() {
13523        let output = run_output(
13524            r#"
13525/// @version 1
13526schema Item {
13527    id: int64
13528    item_name: string
13529}
13530migrate Item from 1 to 2 {
13531    rename_column(item_name, name)
13532}
13533let fields = schema_fields("Item", 2)
13534print(fields)
13535"#,
13536        );
13537        let output_str = &output[0];
13538        assert!(
13539            output_str.contains("name"),
13540            "Expected 'name' in fields, got: {}",
13541            output_str
13542        );
13543        assert!(
13544            !output_str.contains("item_name"),
13545            "Unexpected 'item_name' in fields, got: {}",
13546            output_str
13547        );
13548    }
13549
13550    #[test]
13551    fn test_interp_schema_diff() {
13552        let output = run_output(
13553            r#"
13554schema_register("D", 1, map_from("id", "int64", "name", "string"))
13555schema_register("D", 2, map_from("id", "int64", "name", "string", "email", "string"))
13556let d = schema_diff("D", 1, 2)
13557print(len(d))
13558print(d)
13559"#,
13560        );
13561        assert_eq!(output[0], "1");
13562        assert!(
13563            output[1].contains("added"),
13564            "Expected 'added' in diff, got: {}",
13565            output[1]
13566        );
13567    }
13568
13569    #[test]
13570    fn test_interp_schema_versions() {
13571        let output = run_output(
13572            r#"
13573schema_register("V", 1, map_from("a", "int64"))
13574schema_register("V", 3, map_from("a", "int64", "b", "string"))
13575schema_register("V", 2, map_from("a", "int64", "c", "float64"))
13576print(schema_versions("V"))
13577"#,
13578        );
13579        // Should be sorted
13580        assert_eq!(output, vec!["[1, 2, 3]"]);
13581    }
13582
13583    #[test]
13584    fn test_interp_schema_fields() {
13585        let output = run_output(
13586            r#"
13587schema_register("F", 1, map_from("id", "int64", "name", "string"))
13588let f = schema_fields("F", 1)
13589print(len(f))
13590"#,
13591        );
13592        assert_eq!(output, vec!["2"]);
13593    }
13594
13595    // ── Phase 22: Decimal Tests ────────────────────────────────────
13596
13597    #[test]
13598    fn test_interp_decimal_literal() {
13599        let output = run_output(
13600            r#"
13601let x = 3.14d
13602print(x)
13603"#,
13604        );
13605        assert_eq!(output, vec!["3.14"]);
13606    }
13607
13608    #[test]
13609    fn test_interp_decimal_arithmetic() {
13610        let output = run_output(
13611            r#"
13612let a = 10.50d
13613let b = 3.25d
13614print(a + b)
13615print(a - b)
13616print(a * b)
13617"#,
13618        );
13619        assert_eq!(output, vec!["13.75", "7.25", "34.1250"]);
13620    }
13621
13622    #[test]
13623    fn test_interp_decimal_int_mixed() {
13624        let output = run_output(
13625            r#"
13626let d = 5.5d
13627let i = 2
13628print(d + i)
13629"#,
13630        );
13631        assert_eq!(output, vec!["7.5"]);
13632    }
13633
13634    #[test]
13635    fn test_interp_decimal_comparison() {
13636        let output = run_output(
13637            r#"
13638let a = 1.0d
13639let b = 2.0d
13640print(a < b)
13641print(a == a)
13642"#,
13643        );
13644        assert_eq!(output, vec!["true", "true"]);
13645    }
13646
13647    #[test]
13648    fn test_interp_decimal_negation() {
13649        let output = run_output(
13650            r#"
13651let x = 5.0d
13652print(-x)
13653"#,
13654        );
13655        assert_eq!(output, vec!["-5.0"]);
13656    }
13657
13658    #[test]
13659    fn test_interp_decimal_builtin() {
13660        let output = run_output(
13661            r#"
13662let x = decimal("99.99")
13663print(x)
13664let y = decimal(42)
13665print(y)
13666"#,
13667        );
13668        assert_eq!(output, vec!["99.99", "42"]);
13669    }
13670
13671    #[test]
13672    fn test_interp_decimal_type_of() {
13673        let output = run_output(
13674            r#"
13675let x = 1.0d
13676print(type_of(x))
13677"#,
13678        );
13679        assert_eq!(output, vec!["decimal"]);
13680    }
13681
13682    // ── Phase 23: Security Tests ───────────────────────────────────
13683
13684    #[test]
13685    fn test_interp_secret_set_get() {
13686        let output = run_output(
13687            r#"
13688secret_set("api_key", "abc123")
13689let s = secret_get("api_key")
13690print(s)
13691"#,
13692        );
13693        // Secret display is redacted
13694        assert_eq!(output, vec!["***"]);
13695    }
13696
13697    #[test]
13698    fn test_interp_secret_list_delete() {
13699        let output = run_output(
13700            r#"
13701secret_set("k1", "v1")
13702secret_set("k2", "v2")
13703print(len(secret_list()))
13704secret_delete("k1")
13705print(len(secret_list()))
13706"#,
13707        );
13708        assert_eq!(output, vec!["2", "1"]);
13709    }
13710
13711    #[test]
13712    fn test_interp_mask_email() {
13713        let output = run_output(
13714            r#"
13715print(mask_email("alice@example.com"))
13716"#,
13717        );
13718        assert_eq!(output, vec!["a***@example.com"]);
13719    }
13720
13721    #[test]
13722    fn test_interp_mask_phone() {
13723        let output = run_output(
13724            r#"
13725print(mask_phone("555-123-4567"))
13726"#,
13727        );
13728        assert_eq!(output, vec!["***-***-4567"]);
13729    }
13730
13731    #[test]
13732    fn test_interp_mask_cc() {
13733        let output = run_output(
13734            r#"
13735print(mask_cc("4111111111111111"))
13736"#,
13737        );
13738        assert_eq!(output, vec!["****-****-****-1111"]);
13739    }
13740
13741    #[test]
13742    fn test_interp_redact() {
13743        let output = run_output(
13744            r#"
13745print(redact("sensitive", "full"))
13746print(redact("secret", "partial"))
13747"#,
13748        );
13749        assert_eq!(output, vec!["***", "s***t"]);
13750    }
13751
13752    #[test]
13753    fn test_interp_hash_sha256() {
13754        let output = run_output(
13755            r#"
13756let h = hash("hello", "sha256")
13757print(len(h))
13758"#,
13759        );
13760        assert_eq!(output, vec!["64"]);
13761    }
13762
13763    #[test]
13764    fn test_interp_hash_md5() {
13765        let output = run_output(
13766            r#"
13767let h = hash("hello", "md5")
13768print(len(h))
13769"#,
13770        );
13771        assert_eq!(output, vec!["32"]);
13772    }
13773
13774    #[test]
13775    fn test_interp_check_permission() {
13776        let output = run_output(
13777            r#"
13778print(check_permission("network"))
13779print(check_permission("file_write"))
13780"#,
13781        );
13782        // Without sandbox, everything allowed
13783        assert_eq!(output, vec!["true", "true"]);
13784    }
13785
13786    // ── Phase 25: Async Runtime Tests (feature-gated) ──────────────
13787
13788    #[cfg(not(feature = "async-runtime"))]
13789    #[test]
13790    fn test_interp_async_builtins_require_feature() {
13791        let err = run_err(r#"let t = async_read_file("test.txt")"#);
13792        assert!(
13793            err.contains("async"),
13794            "Expected async feature error, got: {err}"
13795        );
13796    }
13797
13798    #[cfg(feature = "async-runtime")]
13799    #[test]
13800    fn test_interp_async_read_write_file() {
13801        let dir = tempfile::tempdir().unwrap();
13802        let path = dir.path().join("interp_async.txt");
13803        let path_str = path.to_str().unwrap().replace('\\', "/");
13804        let source = format!(
13805            r#"let wt = async_write_file("{path_str}", "interp async")
13806let wr = await(wt)
13807let rt = async_read_file("{path_str}")
13808let content = await(rt)
13809print(content)"#
13810        );
13811        let output = run_output(&source);
13812        assert_eq!(output, vec!["interp async"]);
13813    }
13814
13815    #[cfg(feature = "async-runtime")]
13816    #[test]
13817    fn test_interp_async_sleep() {
13818        let output = run_output(
13819            r#"
13820let t = async_sleep(10)
13821let r = await(t)
13822print(r)
13823"#,
13824        );
13825        assert_eq!(output, vec!["none"]);
13826    }
13827
13828    #[cfg(feature = "async-runtime")]
13829    #[test]
13830    fn test_interp_select() {
13831        let output = run_output(
13832            r#"
13833let fast = async_sleep(10)
13834let slow = async_sleep(5000)
13835let winner = select(fast, slow)
13836let r = await(winner)
13837print(r)
13838"#,
13839        );
13840        assert_eq!(output, vec!["none"]);
13841    }
13842
13843    #[cfg(feature = "async-runtime")]
13844    #[test]
13845    fn test_interp_race_all() {
13846        let output = run_output(
13847            r#"
13848let t1 = async_sleep(10)
13849let t2 = async_sleep(5000)
13850let winner = race_all([t1, t2])
13851let r = await(winner)
13852print(r)
13853"#,
13854        );
13855        assert_eq!(output, vec!["none"]);
13856    }
13857
13858    #[cfg(feature = "async-runtime")]
13859    #[test]
13860    fn test_interp_async_map() {
13861        let output = run_output(
13862            r#"
13863let t = async_map([1, 2, 3], (x) => x * 10)
13864let result = await(t)
13865print(result)
13866"#,
13867        );
13868        assert_eq!(output, vec!["[10, 20, 30]"]);
13869    }
13870
13871    #[cfg(feature = "async-runtime")]
13872    #[test]
13873    fn test_interp_async_filter() {
13874        let output = run_output(
13875            r#"
13876let t = async_filter([1, 2, 3, 4, 5], (x) => x > 3)
13877let result = await(t)
13878print(result)
13879"#,
13880        );
13881        assert_eq!(output, vec!["[4, 5]"]);
13882    }
13883
13884    #[cfg(feature = "async-runtime")]
13885    #[test]
13886    fn test_interp_async_map_empty() {
13887        let output = run_output(
13888            r#"
13889let t = async_map([], (x) => x)
13890let result = await(t)
13891print(result)
13892"#,
13893        );
13894        assert_eq!(output, vec!["[]"]);
13895    }
13896
13897    #[cfg(feature = "async-runtime")]
13898    #[test]
13899    fn test_interp_async_filter_none_match() {
13900        let output = run_output(
13901            r#"
13902let t = async_filter([1, 2, 3], (x) => x > 100)
13903let result = await(t)
13904print(result)
13905"#,
13906        );
13907        assert_eq!(output, vec!["[]"]);
13908    }
13909
13910    // --- Phase 26: Closure upvalue closing tests (interpreter parity) ---
13911
13912    #[test]
13913    fn test_interp_closure_returned_from_function() {
13914        let output = run_output(
13915            r#"
13916fn make_adder(n) {
13917    return (x) => x + n
13918}
13919let add5 = make_adder(5)
13920print(add5(3))
13921print(add5(10))
13922"#,
13923        );
13924        assert_eq!(output, vec!["8", "15"]);
13925    }
13926
13927    #[test]
13928    fn test_interp_closure_factory_multiple_calls() {
13929        let output = run_output(
13930            r#"
13931fn make_adder(n) {
13932    return (x) => x + n
13933}
13934let add2 = make_adder(2)
13935let add10 = make_adder(10)
13936print(add2(5))
13937print(add10(5))
13938print(add2(1))
13939"#,
13940        );
13941        assert_eq!(output, vec!["7", "15", "3"]);
13942    }
13943
13944    #[test]
13945    fn test_interp_closure_returned_in_list() {
13946        let output = run_output(
13947            r#"
13948fn make_ops(n) {
13949    let add = (x) => x + n
13950    let mul = (x) => x * n
13951    return [add, mul]
13952}
13953let ops = make_ops(3)
13954print(ops[0](10))
13955print(ops[1](10))
13956"#,
13957        );
13958        assert_eq!(output, vec!["13", "30"]);
13959    }
13960
13961    #[test]
13962    fn test_interp_nested_closure_return() {
13963        let output = run_output(
13964            r#"
13965fn outer(a) {
13966    fn inner(b) {
13967        return (x) => x + a + b
13968    }
13969    return inner(10)
13970}
13971let f = outer(5)
13972print(f(1))
13973"#,
13974        );
13975        assert_eq!(output, vec!["16"]);
13976    }
13977
13978    #[test]
13979    fn test_interp_multiple_closures_same_local() {
13980        let output = run_output(
13981            r#"
13982fn make_pair(n) {
13983    let inc = (x) => x + n
13984    let dec = (x) => x - n
13985    return [inc, dec]
13986}
13987let pair = make_pair(7)
13988print(pair[0](10))
13989print(pair[1](10))
13990"#,
13991        );
13992        assert_eq!(output, vec!["17", "3"]);
13993    }
13994
13995    #[test]
13996    fn test_interp_closure_captures_multiple_locals() {
13997        let output = run_output(
13998            r#"
13999fn make_greeter(greeting, name) {
14000    let sep = " "
14001    return () => greeting + sep + name
14002}
14003let hi = make_greeter("Hello", "World")
14004let bye = make_greeter("Goodbye", "Alice")
14005print(hi())
14006print(bye())
14007"#,
14008        );
14009        assert_eq!(output, vec!["Hello World", "Goodbye Alice"]);
14010    }
14011
14012    // ── Phase 27: Data Error Hierarchy tests ──
14013
14014    #[test]
14015    fn test_interp_data_error_construct_and_throw() {
14016        let output = run_output(
14017            r#"
14018try {
14019    throw DataError::ParseError("bad format", "file.csv")
14020} catch e {
14021    print(match e { DataError::ParseError(msg, _) => msg, _ => "no match" })
14022    print(match e { DataError::ParseError(_, src) => src, _ => "no match" })
14023}
14024"#,
14025        );
14026        assert_eq!(output, vec!["bad format", "file.csv"]);
14027    }
14028
14029    #[test]
14030    fn test_interp_network_error_construct() {
14031        let output = run_output(
14032            r#"
14033let err = NetworkError::ConnectionError("refused", "localhost")
14034print(match err { NetworkError::ConnectionError(msg, _) => msg, _ => "no match" })
14035print(match err { NetworkError::ConnectionError(_, host) => host, _ => "no match" })
14036"#,
14037        );
14038        assert_eq!(output, vec!["refused", "localhost"]);
14039    }
14040
14041    #[test]
14042    fn test_interp_connector_error_construct() {
14043        let output = run_output(
14044            r#"
14045let err = ConnectorError::QueryError("syntax error", "mysql")
14046print(match err { ConnectorError::QueryError(msg, _) => msg, _ => "no match" })
14047print(match err { ConnectorError::QueryError(_, conn) => conn, _ => "no match" })
14048"#,
14049        );
14050        assert_eq!(output, vec!["syntax error", "mysql"]);
14051    }
14052
14053    #[test]
14054    fn test_interp_is_error_builtin() {
14055        let output = run_output(
14056            r#"
14057let e1 = DataError::NotFound("users")
14058let e2 = NetworkError::TimeoutError("slow")
14059let e3 = 42
14060print(is_error(e1))
14061print(is_error(e2))
14062print(is_error(e3))
14063"#,
14064        );
14065        assert_eq!(output, vec!["true", "true", "false"]);
14066    }
14067
14068    #[test]
14069    fn test_interp_error_type_builtin() {
14070        let output = run_output(
14071            r#"
14072let e = ConnectorError::AuthError("bad key", "redis")
14073print(error_type(e))
14074print(error_type("not an error"))
14075"#,
14076        );
14077        assert_eq!(output, vec!["ConnectorError", "none"]);
14078    }
14079
14080    #[test]
14081    fn test_interp_throw_catch_preserves_enum() {
14082        let output = run_output(
14083            r#"
14084try {
14085    throw DataError::SchemaError("mismatch", "int", "string")
14086} catch e {
14087    print(match e { DataError::SchemaError(msg, _, _) => msg, _ => "no match" })
14088    print(match e { DataError::SchemaError(_, exp, _) => exp, _ => "no match" })
14089    print(match e { DataError::SchemaError(_, _, found) => found, _ => "no match" })
14090}
14091"#,
14092        );
14093        assert_eq!(output, vec!["mismatch", "int", "string"]);
14094    }
14095
14096    // ── Phase 28: Ownership & Move Semantics ──
14097
14098    #[test]
14099    fn test_interp_pipe_moves_value() {
14100        let err = run_err(
14101            r#"
14102fn identity(v) { v }
14103let x = [1, 2, 3]
14104x |> identity()
14105print(x)
14106"#,
14107        );
14108        assert!(err.contains("moved"), "Error should mention 'moved': {err}");
14109    }
14110
14111    #[test]
14112    fn test_interp_clone_before_pipe() {
14113        let output = run_output(
14114            r#"
14115fn identity(v) { v }
14116let x = [1, 2, 3]
14117x.clone() |> identity()
14118print(x)
14119"#,
14120        );
14121        assert_eq!(output, vec!["[1, 2, 3]"]);
14122    }
14123
14124    #[test]
14125    fn test_interp_clone_list_deep() {
14126        let output = run_output(
14127            r#"
14128let original = [1, 2, 3]
14129let copy = original.clone()
14130copy[0] = 99
14131print(original)
14132print(copy)
14133"#,
14134        );
14135        assert_eq!(output, vec!["[1, 2, 3]", "[99, 2, 3]"]);
14136    }
14137
14138    #[test]
14139    fn test_interp_ref_creates_reference() {
14140        let output = run_output(
14141            r#"
14142let x = 42
14143let r = &x
14144print(r)
14145"#,
14146        );
14147        assert_eq!(output, vec!["42"]);
14148    }
14149
14150    #[test]
14151    fn test_interp_parallel_for_basic() {
14152        let output = run_output(
14153            r#"
14154let items = [10, 20, 30]
14155parallel for item in items {
14156    print(item)
14157}
14158"#,
14159        );
14160        assert_eq!(output, vec!["10", "20", "30"]);
14161    }
14162
14163    #[test]
14164    fn test_interp_reassign_after_move() {
14165        let output = run_output(
14166            r#"
14167fn f(x) { x }
14168let x = 1
14169x |> f()
14170let x = 2
14171print(x)
14172"#,
14173        );
14174        assert_eq!(output, vec!["2"]);
14175    }
14176
14177    #[test]
14178    fn test_interp_clone_map() {
14179        let output = run_output(
14180            r#"
14181let m = map_from("a", 1, "b", 2)
14182let m2 = m.clone()
14183print(m)
14184print(m2)
14185"#,
14186        );
14187        assert_eq!(output, vec!["{a: 1, b: 2}", "{a: 1, b: 2}"]);
14188    }
14189
14190    #[test]
14191    fn test_interp_pipe_chain() {
14192        let output = run_output(
14193            r#"
14194fn double(x) { x * 2 }
14195fn add_one(x) { x + 1 }
14196let result = 5 |> double() |> add_one()
14197print(result)
14198"#,
14199        );
14200        assert_eq!(output, vec!["11"]);
14201    }
14202
14203    // ── Phase 34: Agent Framework ──
14204
14205    #[test]
14206    fn test_interp_agent_definition() {
14207        let output = run_output(
14208            r#"
14209fn search(query) { "found: " + query }
14210agent bot {
14211    model: "gpt-4o",
14212    system: "You are helpful.",
14213    tools {
14214        search: {
14215            description: "Search the web",
14216            parameters: {}
14217        }
14218    },
14219    max_turns: 5
14220}
14221print(type_of(bot))
14222print(bot)
14223"#,
14224        );
14225        assert_eq!(output, vec!["agent", "<agent bot>"]);
14226    }
14227
14228    #[test]
14229    fn test_interp_agent_minimal() {
14230        let output = run_output(
14231            r#"
14232agent minimal_bot {
14233    model: "claude-sonnet-4-20250514"
14234}
14235print(type_of(minimal_bot))
14236"#,
14237        );
14238        assert_eq!(output, vec!["agent"]);
14239    }
14240
14241    #[test]
14242    fn test_interp_agent_with_base_url() {
14243        let output = run_output(
14244            r#"
14245agent local_bot {
14246    model: "llama3",
14247    base_url: "http://localhost:11434/v1",
14248    max_turns: 3,
14249    temperature: 0.7
14250}
14251print(local_bot)
14252"#,
14253        );
14254        assert_eq!(output, vec!["<agent local_bot>"]);
14255    }
14256
14257    #[test]
14258    fn test_interp_agent_multiple_tools() {
14259        let output = run_output(
14260            r#"
14261fn search(query) { "result" }
14262fn weather(city) { "sunny" }
14263agent helper {
14264    model: "gpt-4o",
14265    tools {
14266        search: { description: "Search", parameters: {} },
14267        weather: { description: "Get weather", parameters: {} }
14268    }
14269}
14270print(type_of(helper))
14271"#,
14272        );
14273        assert_eq!(output, vec!["agent"]);
14274    }
14275
14276    #[test]
14277    fn test_interp_agent_lifecycle_hooks_stored() {
14278        // Verify that lifecycle hooks are stored as functions
14279        let output = run_output(
14280            r#"
14281fn search(q) { "result" }
14282agent bot {
14283    model: "gpt-4o",
14284    tools {
14285        search: { description: "Search", parameters: {} }
14286    },
14287    on_tool_call {
14288        println("tool: " + tool_name)
14289    }
14290    on_complete {
14291        println("done")
14292    }
14293}
14294print(type_of(bot))
14295print(type_of(__agent_bot_on_tool_call__))
14296print(type_of(__agent_bot_on_complete__))
14297"#,
14298        );
14299        assert_eq!(output, vec!["agent", "function", "function"]);
14300    }
14301
14302    #[test]
14303    fn test_interp_agent_lifecycle_hook_callable() {
14304        // Verify lifecycle hook functions can be called directly
14305        let output = run_output(
14306            r#"
14307agent bot {
14308    model: "gpt-4o",
14309    on_tool_call {
14310        println("called: " + tool_name + " -> " + tool_result)
14311    }
14312    on_complete {
14313        println("completed with " + string(len(result)))
14314    }
14315}
14316__agent_bot_on_tool_call__("search", "query", "found it")
14317"#,
14318        );
14319        assert_eq!(output, vec!["called: search -> found it"]);
14320    }
14321}