1#![allow(clippy::large_enum_variant)]
7use 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#[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#[derive(Debug, Clone)]
35pub struct TlSchema {
36 pub name: String,
37 pub arrow_schema: Arc<ArrowSchema>,
38}
39
40static INTERP_TASK_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
42static INTERP_CHANNEL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
44
45pub 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 TlTask {
71 receiver: Mutex::new(None),
72 id: self.id,
73 }
74 }
75}
76
77pub 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
111static INTERP_GENERATOR_COUNTER: std::sync::atomic::AtomicU64 =
113 std::sync::atomic::AtomicU64::new(1);
114
115pub enum TlGeneratorKind {
117 UserDefined {
119 receiver: Mutex<Option<mpsc::Receiver<Result<Value, String>>>>,
121 resume_tx: mpsc::SyncSender<()>,
123 },
124 ListIter {
126 items: Vec<Value>,
127 index: Mutex<usize>,
128 },
129 Take {
131 source: Arc<TlGenerator>,
132 remaining: Mutex<usize>,
133 },
134 Skip {
136 source: Arc<TlGenerator>,
137 remaining: Mutex<usize>,
138 },
139 Map {
141 source: Arc<TlGenerator>,
142 func: Value,
143 },
144 Filter {
146 source: Arc<TlGenerator>,
147 func: Value,
148 },
149 Chain {
151 first: Arc<TlGenerator>,
152 second: Arc<TlGenerator>,
153 on_second: Mutex<bool>,
154 },
155 Zip {
157 first: Arc<TlGenerator>,
158 second: Arc<TlGenerator>,
159 },
160 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 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#[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#[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 Function {
258 name: String,
259 params: Vec<Param>,
260 body: Vec<Stmt>,
261 is_generator: bool,
262 },
263 Builtin(String),
265 Closure {
267 params: Vec<Param>,
268 body: ClosureBody,
269 captured_env: Vec<HashMap<String, Value>>,
270 },
271 Table(TlTable),
273 Schema(TlSchema),
275 Tensor(tl_ai::TlTensor),
277 Model(tl_ai::TlModel),
279 Connector(ConnectorConfig),
281 Pipeline(PipelineDef),
283 Stream(StreamDef),
285 Agent(tl_stream::AgentDef),
287 StructDef {
289 name: String,
290 fields: Vec<String>,
291 },
292 StructInstance {
294 type_name: String,
295 fields: HashMap<String, Value>,
296 },
297 EnumDef {
299 name: String,
300 variants: Vec<(String, usize)>, },
302 EnumInstance {
304 type_name: String,
305 variant: String,
306 fields: Vec<Value>,
307 },
308 Module {
310 name: String,
311 exports: HashMap<String, Value>,
312 },
313 Map(Vec<(String, Value)>),
315 Set(Vec<Value>),
317 Task(Arc<TlTask>),
319 Channel(Arc<TlChannel>),
321 Generator(Arc<TlGenerator>),
323 Decimal(rust_decimal::Decimal),
325 Secret(String),
327 #[cfg(feature = "python")]
329 PyObject(Arc<InterpPyObjectWrapper>),
330 #[cfg(feature = "mcp")]
332 McpClient(Arc<tl_mcp::McpClient>),
333 Moved,
335 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 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
506enum Signal {
508 None,
509 Return(Value),
510 Break,
511 Continue,
512 Throw(Value),
513 #[allow(dead_code)]
514 Yield(Value),
515}
516
517enum GenSignal {
519 None,
520 Return(Value),
521 Break,
522 Continue,
523 Throw(Value),
524 Yield(#[allow(dead_code)] Value),
525}
526
527#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 global.insert("decimal".to_string(), Value::Builtin("decimal".to_string()));
1094 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 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 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 #[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 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
1299pub struct Interpreter {
1301 pub env: Environment,
1302 pub output: Vec<String>,
1304 last_expr_value: Option<Value>,
1306 data_engine: Option<DataEngine>,
1308 method_table: HashMap<String, HashMap<String, Value>>,
1310 module_cache: HashMap<String, HashMap<String, Value>>,
1312 importing_files: std::collections::HashSet<String>,
1314 pub file_path: Option<String>,
1316 pub test_mode: bool,
1318 pub package_roots: HashMap<String, std::path::PathBuf>,
1320 pub project_root: Option<std::path::PathBuf>,
1322 pub schema_registry: tl_compiler::schema::SchemaRegistry,
1324 pub secret_vault: HashMap<String, String>,
1326 pub security_policy: Option<SecurityPolicy>,
1328 #[cfg(feature = "async-runtime")]
1330 runtime: Option<Arc<tokio::runtime::Runtime>>,
1331 #[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 #[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 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 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(_) => {} }
1405 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 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
1429impl 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 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 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 let Some(ver) = version {
1576 let mut metadata = tl_compiler::schema::SchemaMetadata::default();
1577 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 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 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 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 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 Ok(Signal::None)
1869 }
1870 StmtKind::TraitDef { .. } => {
1871 Ok(Signal::None)
1873 }
1874 StmtKind::TraitImpl {
1875 type_name, methods, ..
1876 } => {
1877 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 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 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 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 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 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 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 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 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 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 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 if let Value::Table(ref tl_table) = left_val {
2256 return self.eval_table_pipe(tl_table.df.clone(), right);
2257 }
2258 if let Expr::Ident(name) = left.as_ref() {
2260 self.env.set(name.clone(), Value::Moved);
2261 }
2262 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 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 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 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 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 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 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 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), }
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 (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 (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 (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 (Value::String(a), Value::String(b)) if *op == BinOp::Add => {
2727 Ok(Value::String(format!("{a}{b}")))
2728 }
2729
2730 (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 (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 (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 (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 (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 (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 (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 (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 *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 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 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 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 self.env.scopes = saved_env;
2952 result
2953 }
2954 _ => Err(runtime_err(format!("Cannot call {}", func.type_name()))),
2955 }
2956 }
2957
2958 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 let (yield_tx, yield_rx) = mpsc::channel::<Result<Value, String>>();
2974 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 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 if resume_rx.recv().is_err() {
2991 return; }
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 }
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 });
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 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 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 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 *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 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 "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 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 "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 "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 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 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 "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" => {
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 Err(runtime_err(
3919 "assert_table_eq() is only available in the VM backend".to_string(),
3920 ))
3921 }
3922 "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 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 "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 "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 "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 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 interp.env.push_scope();
4366 for param in ¶ms {
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 ¶ms {
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 "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 "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 "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 ®ion,
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 "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 "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 "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 "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 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 "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(), };
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 #[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 "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 #[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 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 &[], None, None, None, )
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 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 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 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 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 let _server_handle = tl_mcp::server::serve_stdio_background(server_handler);
7302
7303 while let Ok(req) = rx.recv() {
7305 let result = if let Some(func) = tool_handlers.get(&req.tool_name) {
7306 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 #[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 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 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 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 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 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 if method == "clone" {
7598 return self.deep_clone_interp_value(obj);
7599 }
7600 let obj = match obj {
7602 Value::Ref(inner) => inner.as_ref(),
7603 other => other,
7604 };
7605 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 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 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 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 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 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 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 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 #[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 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 _ => 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 self.exec_import(&file_path, None)?;
8399 let _ = names;
8401 Ok(Signal::None)
8402 }
8403 }
8404 }
8405
8406 fn resolve_use_path(&self, segments: &[String]) -> Result<String, TlError> {
8408 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 let rel_path = segments.join("/");
8429
8430 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 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 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 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 fn exec_import(&mut self, path: &str, alias: Option<&str>) -> Result<Signal, TlError> {
8480 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 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 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 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 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 let exports: HashMap<String, Value> = sub_interp.env.scopes[0].clone();
8535
8536 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 self.module_cache.insert(resolved, exports.clone());
8546
8547 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 if !matches!(v, Value::Builtin(_)) {
8560 self.env.set(k, v);
8561 }
8562 }
8563 }
8564
8565 Ok(Signal::None)
8566 }
8567
8568 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 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 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 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 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 "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 "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 "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 _ => {
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 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 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 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 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 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), );
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 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 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 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 let agg_expr = translate_expr(value, &ctx).map_err(runtime_err)?;
9246 agg_exprs.push(agg_expr.alias(name));
9247 }
9248 _ => {
9249 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 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 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 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 _ => {} }
9335 }
9336
9337 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 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
9368fn 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, },
9381 TypeExpr::Optional(inner) => tl_type_to_arrow(inner), _ => ArrowDataType::Utf8, }
9384}
9385
9386fn 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
9428fn 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
9486fn 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
9508fn 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
9541impl 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 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 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 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 _ => {
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 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 yield_tx
10044 .send(Ok(val.clone()))
10045 .map_err(|_| runtime_err("Generator consumer disconnected".to_string()))?;
10046 resume_rx
10048 .recv()
10049 .map_err(|_| runtime_err("Generator consumer disconnected".to_string()))?;
10050 Ok(val)
10051 }
10052 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 self.eval_expr(&Expr::Assign {
10065 target: target.clone(),
10066 value: Box::new(Expr::None),
10067 })
10068 .ok();
10069 Ok(val)
10071 }
10072 }
10073 }
10074 _ => 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 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 _ => {} }
10112 }
10113
10114 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 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 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 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 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 let features_tensor = match features_val {
10202 Some(Value::Tensor(t)) => t,
10203 Some(Value::List(items)) => {
10204 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 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
10348impl 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 let extract = extract.to_vec();
10379 let transform = transform.to_vec();
10380 let load = load.to_vec();
10381
10382 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 self.env.push_scope();
10390 let mut attempt_ok = true;
10391
10392 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 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 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 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; } 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 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 if let Some(_sink_expr) = sink {
10522 }
10524
10525 self.env.set(name.to_string(), Value::Stream(def));
10527 self.output.push(format!("Stream '{}' declared", name));
10528 Ok(Signal::None)
10529 }
10530
10531 #[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 #[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 #[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 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 #[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 #[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 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 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 messages.push(serde_json::json!({"role": "assistant", "content": &text}));
10768
10769 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 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 #[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 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 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 #[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#[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 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 #[test]
11642 fn test_json_parse_object() {
11643 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[test]
13424 fn test_interp_versioned_schema_registration() {
13425 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 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 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 #[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 #[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 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 assert_eq!(output, vec!["true", "true"]);
13784 }
13785
13786 #[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 #[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 #[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 #[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 #[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 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 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}