Skip to main content

ion_core/
interpreter.rs

1use crate::ast::*;
2use crate::env::Env;
3use crate::error::{ErrorKind, IonError};
4use crate::host_types::TypeRegistry;
5use crate::stdlib::OutputHandler;
6use crate::value::{IonFn, Value};
7use indexmap::IndexMap;
8use std::sync::Arc;
9
10/// Control flow signals that escape normal evaluation.
11enum Signal {
12    Return(Value),
13    Break(Value),
14    Continue,
15}
16
17type IonResult = Result<Value, IonError>;
18type SignalResult = Result<Value, SignalOrError>;
19
20enum SignalOrError {
21    Signal(Signal),
22    Error(IonError),
23}
24
25impl From<IonError> for SignalOrError {
26    fn from(e: IonError) -> Self {
27        SignalOrError::Error(e)
28    }
29}
30
31impl From<Signal> for SignalOrError {
32    fn from(s: Signal) -> Self {
33        SignalOrError::Signal(s)
34    }
35}
36
37#[derive(Clone)]
38pub struct Limits {
39    pub max_call_depth: usize,
40    pub max_loop_iters: usize,
41}
42
43impl Default for Limits {
44    fn default() -> Self {
45        Self {
46            max_call_depth: 512,
47            max_loop_iters: 1_000_000,
48        }
49    }
50}
51
52pub struct Interpreter {
53    pub env: Env,
54    pub limits: Limits,
55    pub types: TypeRegistry,
56    call_depth: usize,
57    #[cfg(feature = "concurrency")]
58    nursery: Option<crate::async_rt::Nursery>,
59    #[cfg(feature = "concurrency")]
60    pub(crate) cancel_flag: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
61}
62
63impl Default for Interpreter {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl Interpreter {
70    pub fn new() -> Self {
71        Self::with_output(crate::stdlib::missing_output_handler())
72    }
73
74    pub fn with_output(output: Arc<dyn OutputHandler>) -> Self {
75        let mut env = Env::new();
76        register_builtins(&mut env, output);
77        Self {
78            env,
79            limits: Limits::default(),
80            types: TypeRegistry::new(),
81            call_depth: 0,
82            #[cfg(feature = "concurrency")]
83            nursery: None,
84            #[cfg(feature = "concurrency")]
85            cancel_flag: None,
86        }
87    }
88
89    pub fn eval_program(&mut self, program: &Program) -> IonResult {
90        match self.eval_stmts(&program.stmts) {
91            Ok(v) => Ok(v),
92            Err(SignalOrError::Error(e)) if e.kind == ErrorKind::PropagatedErr => {
93                Ok(Value::Result(Err(Box::new(Value::Str(e.message.clone())))))
94            }
95            Err(SignalOrError::Error(e)) if e.kind == ErrorKind::PropagatedNone => {
96                Ok(Value::Option(None))
97            }
98            Err(SignalOrError::Error(e)) => Err(e),
99            Err(SignalOrError::Signal(Signal::Return(v))) => Ok(v),
100            Err(SignalOrError::Signal(Signal::Break(_))) => Err(IonError::runtime(
101                ion_str!("break outside of loop").to_string(),
102                0,
103                0,
104            )),
105            Err(SignalOrError::Signal(Signal::Continue)) => Err(IonError::runtime(
106                ion_str!("continue outside of loop").to_string(),
107                0,
108                0,
109            )),
110        }
111    }
112
113    /// Create an interpreter with a pre-existing environment (for VM hybrid mode).
114    pub fn with_env(env: Env) -> Self {
115        Self {
116            env,
117            limits: Limits::default(),
118            types: TypeRegistry::new(),
119            call_depth: 0,
120            #[cfg(feature = "concurrency")]
121            nursery: None,
122            #[cfg(feature = "concurrency")]
123            cancel_flag: None,
124        }
125    }
126
127    /// Take ownership of the environment (for VM hybrid mode).
128    pub fn take_env(self) -> Env {
129        self.env
130    }
131
132    /// Evaluate a block of statements, returning the last value (public for VM).
133    pub fn eval_block(&mut self, stmts: &[Stmt]) -> IonResult {
134        match self.eval_stmts(stmts) {
135            Ok(v) => Ok(v),
136            Err(SignalOrError::Error(e)) => Err(e),
137            Err(SignalOrError::Signal(Signal::Return(v))) => Ok(v),
138            Err(SignalOrError::Signal(Signal::Break(v))) => Ok(v),
139            Err(SignalOrError::Signal(Signal::Continue)) => Ok(Value::Unit),
140        }
141    }
142
143    /// Evaluate a single expression (public for VM).
144    pub fn eval_single_expr(&mut self, expr: &Expr) -> IonResult {
145        match self.eval_expr(expr) {
146            Ok(v) => Ok(v),
147            Err(SignalOrError::Error(e)) => Err(e),
148            Err(SignalOrError::Signal(Signal::Return(v))) => Ok(v),
149            Err(SignalOrError::Signal(Signal::Break(v))) => Ok(v),
150            Err(SignalOrError::Signal(Signal::Continue)) => Ok(Value::Unit),
151        }
152    }
153
154    #[cfg(feature = "concurrency")]
155    fn check_cancelled(&self, line: usize, col: usize) -> Result<(), SignalOrError> {
156        if let Some(flag) = &self.cancel_flag {
157            if flag.load(std::sync::atomic::Ordering::Relaxed) {
158                return Err(IonError::runtime(ion_str!("task cancelled"), line, col).into());
159            }
160        }
161        Ok(())
162    }
163
164    fn eval_stmts(&mut self, stmts: &[Stmt]) -> SignalResult {
165        let mut last = Value::Unit;
166        for (i, stmt) in stmts.iter().enumerate() {
167            let is_last = i == stmts.len() - 1;
168            match &stmt.kind {
169                StmtKind::ExprStmt { expr, has_semi } => {
170                    let val = self.eval_expr(expr)?;
171                    if is_last && !has_semi {
172                        last = val;
173                    } else {
174                        last = Value::Unit;
175                    }
176                }
177                _ => {
178                    self.eval_stmt(stmt)?;
179                    last = Value::Unit;
180                }
181            }
182        }
183        Ok(last)
184    }
185
186    fn eval_stmt(&mut self, stmt: &Stmt) -> SignalResult {
187        match &stmt.kind {
188            StmtKind::Let {
189                mutable,
190                pattern,
191                type_ann,
192                value,
193            } => {
194                let val = self.eval_expr(value)?;
195                if let Some(ann) = type_ann {
196                    Self::check_type_ann(&val, ann, stmt.span)?;
197                }
198                self.bind_pattern(pattern, &val, *mutable, stmt.span)?;
199                Ok(Value::Unit)
200            }
201            StmtKind::FnDecl { name, params, body } => {
202                let captures = self.env.capture();
203                let func = Value::Fn(IonFn::new(
204                    name.clone(),
205                    params.clone(),
206                    body.clone(),
207                    captures,
208                ));
209                self.env.define(name.clone(), func, false);
210                Ok(Value::Unit)
211            }
212            StmtKind::ExprStmt { expr, .. } => {
213                self.eval_expr(expr)?;
214                Ok(Value::Unit)
215            }
216            StmtKind::For {
217                pattern,
218                iter,
219                body,
220            } => {
221                let iter_val = self.eval_expr(iter)?;
222                let items = self.value_to_iter(&iter_val, iter.span)?;
223                for item in items {
224                    #[cfg(feature = "concurrency")]
225                    self.check_cancelled(stmt.span.line, stmt.span.col)?;
226                    self.env.push_scope();
227                    self.bind_pattern(pattern, &item, false, iter.span)?;
228                    match self.eval_stmts(body) {
229                        Ok(_) => {}
230                        Err(SignalOrError::Signal(Signal::Break(_))) => {
231                            self.env.pop_scope();
232                            break;
233                        }
234                        Err(SignalOrError::Signal(Signal::Continue)) => {
235                            self.env.pop_scope();
236                            continue;
237                        }
238                        Err(e) => {
239                            self.env.pop_scope();
240                            return Err(e);
241                        }
242                    }
243                    self.env.pop_scope();
244                }
245                Ok(Value::Unit)
246            }
247            StmtKind::While { cond, body } => {
248                let mut iters = 0usize;
249                loop {
250                    #[cfg(feature = "concurrency")]
251                    self.check_cancelled(stmt.span.line, stmt.span.col)?;
252                    let c = self.eval_expr(cond)?;
253                    if !c.is_truthy() {
254                        break;
255                    }
256                    iters += 1;
257                    if iters > self.limits.max_loop_iters {
258                        return Err(IonError::runtime(
259                            ion_str!("maximum loop iterations exceeded").to_string(),
260                            stmt.span.line,
261                            stmt.span.col,
262                        )
263                        .into());
264                    }
265                    self.env.push_scope();
266                    match self.eval_stmts(body) {
267                        Ok(_) => {}
268                        Err(SignalOrError::Signal(Signal::Break(_))) => {
269                            self.env.pop_scope();
270                            break;
271                        }
272                        Err(SignalOrError::Signal(Signal::Continue)) => {
273                            self.env.pop_scope();
274                            continue;
275                        }
276                        Err(e) => {
277                            self.env.pop_scope();
278                            return Err(e);
279                        }
280                    }
281                    self.env.pop_scope();
282                }
283                Ok(Value::Unit)
284            }
285            StmtKind::WhileLet {
286                pattern,
287                expr,
288                body,
289            } => {
290                let mut iters = 0usize;
291                loop {
292                    #[cfg(feature = "concurrency")]
293                    self.check_cancelled(stmt.span.line, stmt.span.col)?;
294                    let val = self.eval_expr(expr)?;
295                    if !self.pattern_matches(pattern, &val) {
296                        break;
297                    }
298                    iters += 1;
299                    if iters > self.limits.max_loop_iters {
300                        return Err(IonError::runtime(
301                            ion_str!("maximum loop iterations exceeded").to_string(),
302                            stmt.span.line,
303                            stmt.span.col,
304                        )
305                        .into());
306                    }
307                    self.env.push_scope();
308                    self.bind_pattern(pattern, &val, false, expr.span)?;
309                    match self.eval_stmts(body) {
310                        Ok(_) => {}
311                        Err(SignalOrError::Signal(Signal::Break(_))) => {
312                            self.env.pop_scope();
313                            break;
314                        }
315                        Err(SignalOrError::Signal(Signal::Continue)) => {
316                            self.env.pop_scope();
317                            continue;
318                        }
319                        Err(e) => {
320                            self.env.pop_scope();
321                            return Err(e);
322                        }
323                    }
324                    self.env.pop_scope();
325                }
326                Ok(Value::Unit)
327            }
328            StmtKind::Loop { body } => {
329                let mut iters = 0usize;
330                let result = loop {
331                    iters += 1;
332                    if iters > self.limits.max_loop_iters {
333                        return Err(IonError::runtime(
334                            ion_str!("maximum loop iterations exceeded").to_string(),
335                            stmt.span.line,
336                            stmt.span.col,
337                        )
338                        .into());
339                    }
340                    self.env.push_scope();
341                    match self.eval_stmts(body) {
342                        Ok(_) => {}
343                        Err(SignalOrError::Signal(Signal::Break(v))) => {
344                            self.env.pop_scope();
345                            break v;
346                        }
347                        Err(SignalOrError::Signal(Signal::Continue)) => {
348                            self.env.pop_scope();
349                            continue;
350                        }
351                        Err(e) => {
352                            self.env.pop_scope();
353                            return Err(e);
354                        }
355                    }
356                    self.env.pop_scope();
357                };
358                Ok(result)
359            }
360            StmtKind::Break { value } => {
361                let v = match value {
362                    Some(expr) => self.eval_expr(expr)?,
363                    None => Value::Unit,
364                };
365                Err(Signal::Break(v).into())
366            }
367            StmtKind::Continue => Err(Signal::Continue.into()),
368            StmtKind::Return { value } => {
369                let v = match value {
370                    Some(expr) => self.eval_expr(expr)?,
371                    None => Value::Unit,
372                };
373                Err(Signal::Return(v).into())
374            }
375            StmtKind::Assign { target, op, value } => {
376                let rhs = self.eval_expr(value)?;
377                match target {
378                    AssignTarget::Ident(name) => {
379                        let final_val = match op {
380                            AssignOp::Eq => rhs,
381                            _ => {
382                                let lhs = self
383                                    .env
384                                    .get(name)
385                                    .ok_or_else(|| {
386                                        IonError::name(
387                                            format!("{}{}", ion_str!("undefined variable: "), name),
388                                            stmt.span.line,
389                                            stmt.span.col,
390                                        )
391                                    })?
392                                    .clone();
393                                self.apply_compound_op(*op, &lhs, &rhs, stmt.span)?
394                            }
395                        };
396                        self.env
397                            .set(name, final_val)
398                            .map_err(|msg| IonError::runtime(msg, stmt.span.line, stmt.span.col))?;
399                    }
400                    AssignTarget::Index(obj_expr, index_expr) => {
401                        let var_name = match &obj_expr.kind {
402                            ExprKind::Ident(name) => name.clone(),
403                            _ => {
404                                return Err(IonError::runtime(
405                                    ion_str!("index assignment only supported on variables"),
406                                    stmt.span.line,
407                                    stmt.span.col,
408                                )
409                                .into())
410                            }
411                        };
412                        let mut container = self
413                            .env
414                            .get(&var_name)
415                            .ok_or_else(|| {
416                                IonError::name(
417                                    format!("{}{}", ion_str!("undefined variable: "), var_name),
418                                    stmt.span.line,
419                                    stmt.span.col,
420                                )
421                            })?
422                            .clone();
423                        let index = self.eval_expr(index_expr)?;
424                        let final_val = match op {
425                            AssignOp::Eq => rhs,
426                            _ => {
427                                let old = self.index_access(&container, &index, stmt.span)?;
428                                // index_access returns Option-wrapped values; unwrap for compound assign
429                                let old = match old {
430                                    Value::Option(Some(v)) => *v,
431                                    other => other,
432                                };
433                                self.apply_compound_op(*op, &old, &rhs, stmt.span)?
434                            }
435                        };
436                        match (&mut container, &index) {
437                            (Value::List(items), Value::Int(i)) => {
438                                let idx = if *i < 0 { items.len() as i64 + i } else { *i } as usize;
439                                if idx >= items.len() {
440                                    return Err(IonError::runtime(
441                                        format!(
442                                            "{}{}{}",
443                                            ion_str!("index "),
444                                            i,
445                                            ion_str!(" out of range")
446                                        ),
447                                        stmt.span.line,
448                                        stmt.span.col,
449                                    )
450                                    .into());
451                                }
452                                items[idx] = final_val;
453                            }
454                            (Value::Dict(map), Value::Str(key)) => {
455                                map.insert(key.clone(), final_val);
456                            }
457                            _ => {
458                                return Err(IonError::type_err(
459                                    format!(
460                                        "{}{}",
461                                        ion_str!("cannot set index on "),
462                                        container.type_name()
463                                    ),
464                                    stmt.span.line,
465                                    stmt.span.col,
466                                )
467                                .into())
468                            }
469                        }
470                        self.env
471                            .set(&var_name, container)
472                            .map_err(|msg| IonError::runtime(msg, stmt.span.line, stmt.span.col))?;
473                    }
474                    AssignTarget::Field(obj_expr, field) => {
475                        let var_name = match &obj_expr.kind {
476                            ExprKind::Ident(name) => name.clone(),
477                            _ => {
478                                return Err(IonError::runtime(
479                                    ion_str!("field assignment only supported on variables"),
480                                    stmt.span.line,
481                                    stmt.span.col,
482                                )
483                                .into())
484                            }
485                        };
486                        let mut container = self
487                            .env
488                            .get(&var_name)
489                            .ok_or_else(|| {
490                                IonError::name(
491                                    format!("{}{}", ion_str!("undefined variable: "), var_name),
492                                    stmt.span.line,
493                                    stmt.span.col,
494                                )
495                            })?
496                            .clone();
497                        let final_val = match op {
498                            AssignOp::Eq => rhs,
499                            _ => {
500                                let old = self.field_access(&container, field, stmt.span)?;
501                                self.apply_compound_op(*op, &old, &rhs, stmt.span)?
502                            }
503                        };
504                        match &mut container {
505                            Value::Dict(map) => {
506                                map.insert(field.clone(), final_val);
507                            }
508                            Value::HostStruct { fields, .. } => {
509                                if fields.contains_key(field.as_str()) {
510                                    fields.insert(field.clone(), final_val);
511                                } else {
512                                    return Err(IonError::runtime(
513                                        format!(
514                                            "{}{}{}",
515                                            ion_str!("field '"),
516                                            field,
517                                            ion_str!("' not found")
518                                        ),
519                                        stmt.span.line,
520                                        stmt.span.col,
521                                    )
522                                    .into());
523                                }
524                            }
525                            _ => {
526                                return Err(IonError::type_err(
527                                    format!(
528                                        "{}{}",
529                                        ion_str!("cannot set field on "),
530                                        container.type_name()
531                                    ),
532                                    stmt.span.line,
533                                    stmt.span.col,
534                                )
535                                .into())
536                            }
537                        }
538                        self.env
539                            .set(&var_name, container)
540                            .map_err(|msg| IonError::runtime(msg, stmt.span.line, stmt.span.col))?;
541                    }
542                }
543                Ok(Value::Unit)
544            }
545            StmtKind::Use { path, imports } => {
546                // Resolve the module dict by walking the path segments
547                let root = self.env.get(&path[0]).ok_or_else(|| {
548                    SignalOrError::Error(IonError::name(
549                        format!("{}{}", ion_str!("undefined module: "), &path[0]),
550                        stmt.span.line,
551                        stmt.span.col,
552                    ))
553                })?;
554                let mut module_val = root.clone();
555                for seg in &path[1..] {
556                    match &module_val {
557                        Value::Dict(map) => {
558                            module_val = map.get(seg).cloned().ok_or_else(|| {
559                                SignalOrError::Error(IonError::name(
560                                    format!(
561                                        "{}{}{}{}",
562                                        ion_str!("'"),
563                                        seg,
564                                        ion_str!("' not found in module "),
565                                        &path[0]
566                                    ),
567                                    stmt.span.line,
568                                    stmt.span.col,
569                                ))
570                            })?;
571                        }
572                        _ => {
573                            return Err(IonError::type_err(
574                                format!(
575                                    "{}{}{}",
576                                    ion_str!("'"),
577                                    seg,
578                                    ion_str!("' is not a module")
579                                ),
580                                stmt.span.line,
581                                stmt.span.col,
582                            )
583                            .into())
584                        }
585                    }
586                }
587                // Now import from module_val (which should be a dict)
588                match imports {
589                    UseImports::Glob => {
590                        if let Value::Dict(map) = &module_val {
591                            for (name, val) in map {
592                                self.env.define(name.clone(), val.clone(), false);
593                            }
594                        } else {
595                            return Err(IonError::type_err(
596                                ion_str!("use target is not a module"),
597                                stmt.span.line,
598                                stmt.span.col,
599                            )
600                            .into());
601                        }
602                    }
603                    UseImports::Names(names) => {
604                        if let Value::Dict(map) = &module_val {
605                            for name in names {
606                                let val = map.get(name).ok_or_else(|| {
607                                    SignalOrError::Error(IonError::name(
608                                        format!(
609                                            "{}{}{}",
610                                            ion_str!("'"),
611                                            name,
612                                            ion_str!("' not found in module")
613                                        ),
614                                        stmt.span.line,
615                                        stmt.span.col,
616                                    ))
617                                })?;
618                                self.env.define(name.clone(), val.clone(), false);
619                            }
620                        } else {
621                            return Err(IonError::type_err(
622                                ion_str!("use target is not a module"),
623                                stmt.span.line,
624                                stmt.span.col,
625                            )
626                            .into());
627                        }
628                    }
629                    UseImports::Single(name) => {
630                        if let Value::Dict(map) = &module_val {
631                            let val = map.get(name).ok_or_else(|| {
632                                SignalOrError::Error(IonError::name(
633                                    format!(
634                                        "{}{}{}",
635                                        ion_str!("'"),
636                                        name,
637                                        ion_str!("' not found in module")
638                                    ),
639                                    stmt.span.line,
640                                    stmt.span.col,
641                                ))
642                            })?;
643                            self.env.define(name.clone(), val.clone(), false);
644                        } else {
645                            return Err(IonError::type_err(
646                                ion_str!("use target is not a module"),
647                                stmt.span.line,
648                                stmt.span.col,
649                            )
650                            .into());
651                        }
652                    }
653                }
654                Ok(Value::Unit)
655            }
656        }
657    }
658
659    fn eval_expr(&mut self, expr: &Expr) -> SignalResult {
660        let span = expr.span;
661        match &expr.kind {
662            ExprKind::Int(n) => Ok(Value::Int(*n)),
663            ExprKind::Float(n) => Ok(Value::Float(*n)),
664            ExprKind::Bool(b) => Ok(Value::Bool(*b)),
665            ExprKind::Str(s) => Ok(Value::Str(s.clone())),
666            ExprKind::Bytes(b) => Ok(Value::Bytes(b.clone())),
667            ExprKind::None => Ok(Value::Option(None)),
668            ExprKind::Unit => Ok(Value::Unit),
669
670            ExprKind::FStr(parts) => {
671                let mut result = String::new();
672                for part in parts {
673                    match part {
674                        FStrPart::Literal(s) => result.push_str(s),
675                        FStrPart::Expr(e) => {
676                            let val = self.eval_expr(e)?;
677                            result.push_str(&val.to_string());
678                        }
679                    }
680                }
681                Ok(Value::Str(result))
682            }
683
684            ExprKind::Ident(name) => self.env.get(name).cloned().ok_or_else(|| {
685                IonError::name(
686                    format!("{}{}", ion_str!("undefined variable: "), name),
687                    span.line,
688                    span.col,
689                )
690                .into()
691            }),
692
693            ExprKind::ModulePath(segments) => {
694                // Resolve a::b::c by walking dict fields from the root module
695                let root = self.env.get(&segments[0]).ok_or_else(|| {
696                    SignalOrError::Error(IonError::name(
697                        format!("{}{}", ion_str!("undefined module: "), &segments[0]),
698                        span.line,
699                        span.col,
700                    ))
701                })?;
702                let mut current = root.clone();
703                for seg in &segments[1..] {
704                    match &current {
705                        Value::Dict(map) => {
706                            current = map.get(seg).cloned().ok_or_else(|| {
707                                SignalOrError::Error(IonError::name(
708                                    format!(
709                                        "{}{}{}{}",
710                                        ion_str!("'"),
711                                        seg,
712                                        ion_str!("' not found in module "),
713                                        &segments[0]
714                                    ),
715                                    span.line,
716                                    span.col,
717                                ))
718                            })?;
719                        }
720                        _ => {
721                            return Err(IonError::type_err(
722                                format!(
723                                    "{}{}{}",
724                                    ion_str!("cannot access '"),
725                                    seg,
726                                    ion_str!("' on non-module value")
727                                ),
728                                span.line,
729                                span.col,
730                            )
731                            .into())
732                        }
733                    }
734                }
735                Ok(current)
736            }
737
738            ExprKind::SomeExpr(e) => {
739                let val = self.eval_expr(e)?;
740                Ok(Value::Option(Some(Box::new(val))))
741            }
742            ExprKind::OkExpr(e) => {
743                let val = self.eval_expr(e)?;
744                Ok(Value::Result(Ok(Box::new(val))))
745            }
746            ExprKind::ErrExpr(e) => {
747                let val = self.eval_expr(e)?;
748                Ok(Value::Result(Err(Box::new(val))))
749            }
750
751            ExprKind::List(items) => {
752                let mut vals = Vec::new();
753                for entry in items {
754                    match entry {
755                        ListEntry::Elem(expr) => vals.push(self.eval_expr(expr)?),
756                        ListEntry::Spread(expr) => match self.eval_expr(expr)? {
757                            Value::List(sub) => vals.extend(sub),
758                            other => {
759                                return Err(IonError::type_err(
760                                    format!(
761                                        "{}{}",
762                                        ion_str!("spread requires a list, got "),
763                                        other.type_name()
764                                    ),
765                                    span.line,
766                                    span.col,
767                                )
768                                .into())
769                            }
770                        },
771                    }
772                }
773                Ok(Value::List(vals))
774            }
775            ExprKind::Dict(entries) => {
776                let mut map = IndexMap::new();
777                for entry in entries {
778                    match entry {
779                        DictEntry::KeyValue(k, v) => {
780                            let key = self.eval_expr(k)?;
781                            let key_str = match key {
782                                Value::Str(s) => s,
783                                _ => {
784                                    return Err(IonError::type_err(
785                                        ion_str!("dict keys must be strings").to_string(),
786                                        span.line,
787                                        span.col,
788                                    )
789                                    .into())
790                                }
791                            };
792                            let val = self.eval_expr(v)?;
793                            map.insert(key_str, val);
794                        }
795                        DictEntry::Spread(expr) => {
796                            let val = self.eval_expr(expr)?;
797                            match val {
798                                Value::Dict(other) => {
799                                    for (k, v) in other {
800                                        map.insert(k, v);
801                                    }
802                                }
803                                _ => {
804                                    return Err(IonError::type_err(
805                                        ion_str!("spread requires a dict").to_string(),
806                                        span.line,
807                                        span.col,
808                                    )
809                                    .into())
810                                }
811                            }
812                        }
813                    }
814                }
815                Ok(Value::Dict(map))
816            }
817            ExprKind::Tuple(items) => {
818                let mut vals = Vec::new();
819                for item in items {
820                    vals.push(self.eval_expr(item)?);
821                }
822                Ok(Value::Tuple(vals))
823            }
824
825            ExprKind::ListComp {
826                expr,
827                pattern,
828                iter,
829                cond,
830            } => {
831                let iter_val = self.eval_expr(iter)?;
832                let items = self.value_to_iter(&iter_val, span)?;
833                let mut result = Vec::new();
834                for item in items {
835                    self.env.push_scope();
836                    self.bind_pattern(pattern, &item, false, span)?;
837                    let include = if let Some(c) = cond {
838                        let v = self.eval_expr(c)?;
839                        v.is_truthy()
840                    } else {
841                        true
842                    };
843                    if include {
844                        result.push(self.eval_expr(expr)?);
845                    }
846                    self.env.pop_scope();
847                }
848                Ok(Value::List(result))
849            }
850            ExprKind::DictComp {
851                key,
852                value,
853                pattern,
854                iter,
855                cond,
856            } => {
857                let iter_val = self.eval_expr(iter)?;
858                let items = self.value_to_iter(&iter_val, span)?;
859                let mut map = IndexMap::new();
860                for item in items {
861                    self.env.push_scope();
862                    self.bind_pattern(pattern, &item, false, span)?;
863                    let include = if let Some(c) = cond {
864                        let v = self.eval_expr(c)?;
865                        v.is_truthy()
866                    } else {
867                        true
868                    };
869                    if include {
870                        let k = self.eval_expr(key)?;
871                        let k_str = match k {
872                            Value::Str(s) => s,
873                            _ => {
874                                return Err(IonError::type_err(
875                                    ion_str!("dict comp keys must be strings").to_string(),
876                                    span.line,
877                                    span.col,
878                                )
879                                .into())
880                            }
881                        };
882                        let v = self.eval_expr(value)?;
883                        map.insert(k_str, v);
884                    }
885                    self.env.pop_scope();
886                }
887                Ok(Value::Dict(map))
888            }
889
890            ExprKind::BinOp { left, op, right } => {
891                // Short-circuit for && and ||
892                if matches!(op, BinOp::And) {
893                    let l = self.eval_expr(left)?;
894                    if !l.is_truthy() {
895                        return Ok(Value::Bool(false));
896                    }
897                    let r = self.eval_expr(right)?;
898                    return Ok(Value::Bool(r.is_truthy()));
899                }
900                if matches!(op, BinOp::Or) {
901                    let l = self.eval_expr(left)?;
902                    if l.is_truthy() {
903                        return Ok(Value::Bool(true));
904                    }
905                    let r = self.eval_expr(right)?;
906                    return Ok(Value::Bool(r.is_truthy()));
907                }
908                let l = self.eval_expr(left)?;
909                let r = self.eval_expr(right)?;
910                self.eval_binop(*op, &l, &r, span)
911            }
912
913            ExprKind::UnaryOp { op, expr } => {
914                let val = self.eval_expr(expr)?;
915                match op {
916                    UnaryOp::Neg => match val {
917                        Value::Int(n) => Ok(Value::Int(-n)),
918                        Value::Float(n) => Ok(Value::Float(-n)),
919                        _ => Err(IonError::type_err(
920                            format!("{}{}", ion_str!("cannot negate "), val.type_name()),
921                            span.line,
922                            span.col,
923                        )
924                        .into()),
925                    },
926                    UnaryOp::Not => Ok(Value::Bool(!val.is_truthy())),
927                }
928            }
929
930            ExprKind::Try(inner) => {
931                let val = self.eval_expr(inner)?;
932                match val {
933                    Value::Result(Ok(v)) => Ok(*v),
934                    Value::Result(Err(e)) => {
935                        Err(IonError::propagated_err(e.to_string(), span.line, span.col).into())
936                    }
937                    Value::Option(Some(v)) => Ok(*v),
938                    Value::Option(None) => {
939                        Err(IonError::propagated_none(span.line, span.col).into())
940                    }
941                    _ => Err(IonError::type_err(
942                        format!(
943                            "{}{}",
944                            ion_str!("? applied to non-Result/Option: "),
945                            val.type_name()
946                        ),
947                        span.line,
948                        span.col,
949                    )
950                    .into()),
951                }
952            }
953
954            ExprKind::PipeOp { left, right } => {
955                let lval = self.eval_expr(left)?;
956                // right should be a Call — insert lval as first argument
957                match &right.kind {
958                    ExprKind::Call { func, args } => {
959                        let mut new_args = vec![CallArg {
960                            name: None,
961                            value: Expr {
962                                kind: ExprKind::Int(0),
963                                span, // placeholder
964                            },
965                        }];
966                        new_args.extend(args.iter().cloned());
967                        let func_val = self.eval_expr(func)?;
968                        let mut arg_vals = vec![lval];
969                        for arg in args {
970                            arg_vals.push(self.eval_expr(&arg.value)?);
971                        }
972                        self.call_value(&func_val, &arg_vals, span)
973                    }
974                    ExprKind::Ident(_) => {
975                        // Bare function name, call with lval as only arg
976                        let func_val = self.eval_expr(right)?;
977                        self.call_value(&func_val, &[lval], span)
978                    }
979                    _ => Err(IonError::runtime(
980                        ion_str!("right side of |> must be a function call").to_string(),
981                        span.line,
982                        span.col,
983                    )
984                    .into()),
985                }
986            }
987
988            ExprKind::FieldAccess { expr, field } => {
989                let val = self.eval_expr(expr)?;
990                self.field_access(&val, field, span)
991            }
992
993            ExprKind::Index { expr, index } => {
994                let val = self.eval_expr(expr)?;
995                let idx = self.eval_expr(index)?;
996                self.index_access(&val, &idx, span)
997            }
998
999            ExprKind::Slice {
1000                expr,
1001                start,
1002                end,
1003                inclusive,
1004            } => {
1005                let val = self.eval_expr(expr)?;
1006                let s = match start {
1007                    Some(e) => Some(self.eval_expr(e)?),
1008                    None => None,
1009                };
1010                let e = match end {
1011                    Some(e) => Some(self.eval_expr(e)?),
1012                    None => None,
1013                };
1014                self.slice_access(&val, s.as_ref(), e.as_ref(), *inclusive, span)
1015            }
1016
1017            ExprKind::MethodCall { expr, method, args } => {
1018                let receiver = self.eval_expr(expr)?;
1019                let mut arg_vals = Vec::new();
1020                for arg in args {
1021                    arg_vals.push(self.eval_expr(&arg.value)?);
1022                }
1023                self.method_call(&receiver, method, &arg_vals, span)
1024            }
1025
1026            ExprKind::Call { func, args } => {
1027                let func_val = self.eval_expr(func)?;
1028                let has_named = args.iter().any(|a| a.name.is_some());
1029                if has_named {
1030                    let mut evaluated: Vec<(Option<String>, Value)> = Vec::new();
1031                    for arg in args {
1032                        evaluated.push((arg.name.clone(), self.eval_expr(&arg.value)?));
1033                    }
1034                    self.call_with_named(&func_val, evaluated, span)
1035                } else {
1036                    let mut arg_vals = Vec::new();
1037                    for arg in args {
1038                        arg_vals.push(self.eval_expr(&arg.value)?);
1039                    }
1040                    self.call_value(&func_val, &arg_vals, span)
1041                }
1042            }
1043
1044            ExprKind::Lambda { params, body } => {
1045                let captures = self.env.capture();
1046                let fn_params: Vec<Param> = params
1047                    .iter()
1048                    .map(|p| Param {
1049                        name: p.clone(),
1050                        default: None,
1051                    })
1052                    .collect();
1053                // Wrap body expr into a block with one ExprStmt
1054                let body_stmts = vec![Stmt {
1055                    kind: StmtKind::ExprStmt {
1056                        expr: (**body).clone(),
1057                        has_semi: false,
1058                    },
1059                    span,
1060                }];
1061                Ok(Value::Fn(IonFn::new(
1062                    ion_str!("<lambda>").to_string(),
1063                    fn_params,
1064                    body_stmts,
1065                    captures,
1066                )))
1067            }
1068
1069            ExprKind::If {
1070                cond,
1071                then_body,
1072                else_body,
1073            } => {
1074                let c = self.eval_expr(cond)?;
1075                self.env.push_scope();
1076                let result = if c.is_truthy() {
1077                    self.eval_stmts(then_body)
1078                } else if let Some(else_stmts) = else_body {
1079                    self.eval_stmts(else_stmts)
1080                } else {
1081                    Ok(Value::Unit)
1082                };
1083                self.env.pop_scope();
1084                result
1085            }
1086
1087            ExprKind::IfLet {
1088                pattern,
1089                expr,
1090                then_body,
1091                else_body,
1092            } => {
1093                let val = self.eval_expr(expr)?;
1094                if self.pattern_matches(pattern, &val) {
1095                    self.env.push_scope();
1096                    self.bind_pattern(pattern, &val, false, span)?;
1097                    let result = self.eval_stmts(then_body);
1098                    self.env.pop_scope();
1099                    result
1100                } else if let Some(else_stmts) = else_body {
1101                    self.env.push_scope();
1102                    let result = self.eval_stmts(else_stmts);
1103                    self.env.pop_scope();
1104                    result
1105                } else {
1106                    Ok(Value::Unit)
1107                }
1108            }
1109
1110            ExprKind::Match { expr, arms } => {
1111                let val = self.eval_expr(expr)?;
1112                for arm in arms {
1113                    if self.pattern_matches(&arm.pattern, &val) {
1114                        self.env.push_scope();
1115                        self.bind_pattern(&arm.pattern, &val, false, span)?;
1116                        if let Some(guard) = &arm.guard {
1117                            let guard_val = self.eval_expr(guard)?;
1118                            if !guard_val.is_truthy() {
1119                                self.env.pop_scope();
1120                                continue;
1121                            }
1122                        }
1123                        let result = self.eval_expr(&arm.body);
1124                        self.env.pop_scope();
1125                        return result;
1126                    }
1127                }
1128                Err(IonError::runtime(
1129                    ion_str!("non-exhaustive match").to_string(),
1130                    span.line,
1131                    span.col,
1132                )
1133                .into())
1134            }
1135
1136            ExprKind::Block(stmts) => {
1137                self.env.push_scope();
1138                let result = self.eval_stmts(stmts);
1139                self.env.pop_scope();
1140                result
1141            }
1142
1143            ExprKind::LoopExpr(body) => {
1144                let result = loop {
1145                    self.env.push_scope();
1146                    match self.eval_stmts(body) {
1147                        Ok(_) => {}
1148                        Err(SignalOrError::Signal(Signal::Break(v))) => {
1149                            self.env.pop_scope();
1150                            break v;
1151                        }
1152                        Err(SignalOrError::Signal(Signal::Continue)) => {
1153                            self.env.pop_scope();
1154                            continue;
1155                        }
1156                        Err(e) => {
1157                            self.env.pop_scope();
1158                            return Err(e);
1159                        }
1160                    }
1161                    self.env.pop_scope();
1162                };
1163                Ok(result)
1164            }
1165
1166            ExprKind::TryCatch { body, var, handler } => {
1167                self.env.push_scope();
1168                let result = self.eval_stmts(body);
1169                self.env.pop_scope();
1170                match result {
1171                    Ok(v) => Ok(v),
1172                    Err(SignalOrError::Signal(s)) => {
1173                        // Signals (return/break/continue) pass through — not errors
1174                        Err(SignalOrError::Signal(s))
1175                    }
1176                    Err(SignalOrError::Error(e)) => {
1177                        // Catch the error: bind error message to `var`, run handler
1178                        self.env.push_scope();
1179                        self.env
1180                            .define(var.clone(), Value::Str(e.message.clone()), false);
1181                        let handler_result = self.eval_stmts(handler);
1182                        self.env.pop_scope();
1183                        handler_result
1184                    }
1185                }
1186            }
1187
1188            ExprKind::Range {
1189                start,
1190                end,
1191                inclusive,
1192            } => {
1193                let s = self.eval_expr(start)?;
1194                let e = self.eval_expr(end)?;
1195                match (&s, &e) {
1196                    (Value::Int(a), Value::Int(b)) => Ok(Value::Range {
1197                        start: *a,
1198                        end: *b,
1199                        inclusive: *inclusive,
1200                    }),
1201                    _ => Err(IonError::type_err(
1202                        ion_str!("range requires integer bounds").to_string(),
1203                        span.line,
1204                        span.col,
1205                    )
1206                    .into()),
1207                }
1208            }
1209
1210            ExprKind::StructConstruct {
1211                name,
1212                fields,
1213                spread,
1214            } => {
1215                let mut field_map = IndexMap::new();
1216                if let Some(spread_expr) = spread {
1217                    let spread_val = self.eval_expr(spread_expr)?;
1218                    match spread_val {
1219                        Value::HostStruct { fields: sf, .. } => {
1220                            for (k, v) in sf {
1221                                field_map.insert(k, v);
1222                            }
1223                        }
1224                        _ => {
1225                            return Err(IonError::type_err(
1226                                ion_str!("spread in struct constructor requires a struct")
1227                                    .to_string(),
1228                                span.line,
1229                                span.col,
1230                            )
1231                            .into())
1232                        }
1233                    }
1234                }
1235                for (fname, fexpr) in fields {
1236                    let val = self.eval_expr(fexpr)?;
1237                    field_map.insert(fname.clone(), val);
1238                }
1239                self.types
1240                    .construct_struct(name, field_map)
1241                    .map_err(|msg| IonError::runtime(msg, span.line, span.col).into())
1242            }
1243            ExprKind::EnumVariant { enum_name, variant } => self
1244                .types
1245                .construct_enum(enum_name, variant, vec![])
1246                .map_err(|msg| IonError::runtime(msg, span.line, span.col).into()),
1247            ExprKind::EnumVariantCall {
1248                enum_name,
1249                variant,
1250                args,
1251            } => {
1252                let mut vals = Vec::new();
1253                for arg in args {
1254                    vals.push(self.eval_expr(arg)?);
1255                }
1256                self.types
1257                    .construct_enum(enum_name, variant, vals)
1258                    .map_err(|msg| IonError::runtime(msg, span.line, span.col).into())
1259            }
1260
1261            // Concurrency
1262            #[cfg(feature = "concurrency")]
1263            ExprKind::AsyncBlock(body) => self.eval_async_block(body, span),
1264            #[cfg(feature = "concurrency")]
1265            ExprKind::SpawnExpr(expr) => self.eval_spawn(expr, span),
1266            #[cfg(feature = "concurrency")]
1267            ExprKind::AwaitExpr(expr) => self.eval_await(expr, span),
1268            #[cfg(feature = "concurrency")]
1269            ExprKind::SelectExpr(branches) => self.eval_select(branches, span),
1270
1271            #[cfg(not(feature = "concurrency"))]
1272            ExprKind::AsyncBlock(_)
1273            | ExprKind::SpawnExpr(_)
1274            | ExprKind::AwaitExpr(_)
1275            | ExprKind::SelectExpr(_) => Err(IonError::runtime(
1276                ion_str!("concurrency features require the 'concurrency' cargo feature")
1277                    .to_string(),
1278                span.line,
1279                span.col,
1280            )
1281            .into()),
1282        }
1283    }
1284
1285    // --- Helpers ---
1286
1287    fn eval_binop(&self, op: BinOp, l: &Value, r: &Value, span: Span) -> SignalResult {
1288        match op {
1289            BinOp::Add => match (l, r) {
1290                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)),
1291                (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
1292                (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
1293                (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + *b as f64)),
1294                (Value::Str(a), Value::Str(b)) => Ok(Value::Str(format!("{}{}", a, b))),
1295                (Value::Bytes(a), Value::Bytes(b)) => {
1296                    let mut result = a.clone();
1297                    result.extend(b);
1298                    Ok(Value::Bytes(result))
1299                }
1300                _ => Err(self.type_mismatch_err(ion_str!("+"), l, r, span)),
1301            },
1302            BinOp::Sub => match (l, r) {
1303                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)),
1304                (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)),
1305                (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 - b)),
1306                (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a - *b as f64)),
1307                _ => Err(self.type_mismatch_err(ion_str!("-"), l, r, span)),
1308            },
1309            BinOp::Mul => match (l, r) {
1310                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)),
1311                (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)),
1312                (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 * b)),
1313                (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a * *b as f64)),
1314                (Value::Str(s), Value::Int(n)) | (Value::Int(n), Value::Str(s)) => {
1315                    Ok(Value::Str(s.repeat(*n as usize)))
1316                }
1317                _ => Err(self.type_mismatch_err(ion_str!("*"), l, r, span)),
1318            },
1319            BinOp::Div => match (l, r) {
1320                (Value::Int(a), Value::Int(b)) => {
1321                    if *b == 0 {
1322                        Err(IonError::runtime(
1323                            ion_str!("division by zero").to_string(),
1324                            span.line,
1325                            span.col,
1326                        )
1327                        .into())
1328                    } else {
1329                        Ok(Value::Int(a / b))
1330                    }
1331                }
1332                (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a / b)),
1333                (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 / b)),
1334                (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a / *b as f64)),
1335                _ => Err(self.type_mismatch_err(ion_str!("/"), l, r, span)),
1336            },
1337            BinOp::Mod => match (l, r) {
1338                (Value::Int(a), Value::Int(b)) => {
1339                    if *b == 0 {
1340                        Err(IonError::runtime(
1341                            ion_str!("modulo by zero").to_string(),
1342                            span.line,
1343                            span.col,
1344                        )
1345                        .into())
1346                    } else {
1347                        Ok(Value::Int(a % b))
1348                    }
1349                }
1350                _ => Err(self.type_mismatch_err(ion_str!("%"), l, r, span)),
1351            },
1352            BinOp::Eq => Ok(Value::Bool(l == r)),
1353            BinOp::Ne => Ok(Value::Bool(l != r)),
1354            BinOp::Lt => self.compare_values(l, r, span, |o| o == std::cmp::Ordering::Less),
1355            BinOp::Gt => self.compare_values(l, r, span, |o| o == std::cmp::Ordering::Greater),
1356            BinOp::Le => self.compare_values(l, r, span, |o| o != std::cmp::Ordering::Greater),
1357            BinOp::Ge => self.compare_values(l, r, span, |o| o != std::cmp::Ordering::Less),
1358            BinOp::And | BinOp::Or => unreachable!(), // handled in eval_expr
1359            BinOp::BitAnd => match (l, r) {
1360                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a & b)),
1361                _ => Err(self.type_mismatch_err(ion_str!("&"), l, r, span)),
1362            },
1363            BinOp::BitOr => match (l, r) {
1364                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a | b)),
1365                _ => Err(self.type_mismatch_err(ion_str!("|"), l, r, span)),
1366            },
1367            BinOp::BitXor => match (l, r) {
1368                (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a ^ b)),
1369                _ => Err(self.type_mismatch_err(ion_str!("^"), l, r, span)),
1370            },
1371            BinOp::Shl => match (l, r) {
1372                (Value::Int(a), Value::Int(b)) if (0..64).contains(b) => Ok(Value::Int(a << b)),
1373                (Value::Int(_), Value::Int(b)) => Err(IonError::runtime(
1374                    format!("shift count {} is out of range 0..64", b),
1375                    span.line,
1376                    span.col,
1377                )
1378                .into()),
1379                _ => Err(self.type_mismatch_err(ion_str!("<<"), l, r, span)),
1380            },
1381            BinOp::Shr => match (l, r) {
1382                (Value::Int(a), Value::Int(b)) if (0..64).contains(b) => Ok(Value::Int(a >> b)),
1383                (Value::Int(_), Value::Int(b)) => Err(IonError::runtime(
1384                    format!("shift count {} is out of range 0..64", b),
1385                    span.line,
1386                    span.col,
1387                )
1388                .into()),
1389                _ => Err(self.type_mismatch_err(ion_str!(">>"), l, r, span)),
1390            },
1391        }
1392    }
1393
1394    fn compare_values(
1395        &self,
1396        l: &Value,
1397        r: &Value,
1398        span: Span,
1399        f: impl Fn(std::cmp::Ordering) -> bool,
1400    ) -> SignalResult {
1401        let ord = match (l, r) {
1402            (Value::Int(a), Value::Int(b)) => a.cmp(b),
1403            (Value::Float(a), Value::Float(b)) => {
1404                a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
1405            }
1406            (Value::Int(a), Value::Float(b)) => (*a as f64)
1407                .partial_cmp(b)
1408                .unwrap_or(std::cmp::Ordering::Equal),
1409            (Value::Float(a), Value::Int(b)) => a
1410                .partial_cmp(&(*b as f64))
1411                .unwrap_or(std::cmp::Ordering::Equal),
1412            (Value::Str(a), Value::Str(b)) => a.cmp(b),
1413            _ => return Err(self.type_mismatch_err(ion_str!("compare"), l, r, span)),
1414        };
1415        Ok(Value::Bool(f(ord)))
1416    }
1417
1418    fn type_mismatch_err(
1419        &self,
1420        op: impl std::fmt::Display,
1421        l: &Value,
1422        r: &Value,
1423        span: Span,
1424    ) -> SignalOrError {
1425        IonError::type_err(
1426            format!(
1427                "{}{}{}{}{}{}",
1428                ion_str!("cannot apply '"),
1429                op,
1430                ion_str!("' to "),
1431                l.type_name(),
1432                ion_str!(" and "),
1433                r.type_name(),
1434            ),
1435            span.line,
1436            span.col,
1437        )
1438        .into()
1439    }
1440
1441    fn apply_compound_op(
1442        &self,
1443        op: AssignOp,
1444        lhs: &Value,
1445        rhs: &Value,
1446        span: Span,
1447    ) -> SignalResult {
1448        match op {
1449            AssignOp::PlusEq => self.eval_binop(BinOp::Add, lhs, rhs, span),
1450            AssignOp::MinusEq => self.eval_binop(BinOp::Sub, lhs, rhs, span),
1451            AssignOp::StarEq => self.eval_binop(BinOp::Mul, lhs, rhs, span),
1452            AssignOp::SlashEq => self.eval_binop(BinOp::Div, lhs, rhs, span),
1453            AssignOp::Eq => unreachable!(),
1454        }
1455    }
1456
1457    fn field_access(&self, val: &Value, field: &str, span: Span) -> SignalResult {
1458        match val {
1459            Value::Dict(map) => Ok(match map.get(field) {
1460                Some(v) => v.clone(),
1461                None => Value::Option(None),
1462            }),
1463            Value::HostStruct { fields, .. } => Ok(match fields.get(field) {
1464                Some(v) => v.clone(),
1465                None => {
1466                    return Err(IonError::type_err(
1467                        format!(
1468                            "{}{}{}",
1469                            ion_str!("no field '"),
1470                            field,
1471                            ion_str!("' on struct")
1472                        ),
1473                        span.line,
1474                        span.col,
1475                    )
1476                    .into())
1477                }
1478            }),
1479            _ => Err(IonError::type_err(
1480                format!("{}{}", ion_str!("cannot access field on "), val.type_name()),
1481                span.line,
1482                span.col,
1483            )
1484            .into()),
1485        }
1486    }
1487
1488    fn index_access(&self, val: &Value, idx: &Value, span: Span) -> SignalResult {
1489        match (val, idx) {
1490            (Value::List(items), Value::Int(i)) => {
1491                let index = if *i < 0 { items.len() as i64 + i } else { *i } as usize;
1492                items.get(index).cloned().ok_or_else(|| {
1493                    IonError::runtime(
1494                        ion_str!("list index out of bounds").to_string(),
1495                        span.line,
1496                        span.col,
1497                    )
1498                    .into()
1499                })
1500            }
1501            (Value::Dict(map), Value::Str(key)) => Ok(match map.get(key.as_str()) {
1502                Some(v) => v.clone(),
1503                None => Value::Option(None),
1504            }),
1505            (Value::Bytes(bytes), Value::Int(i)) => {
1506                let index = if *i < 0 { bytes.len() as i64 + i } else { *i } as usize;
1507                bytes
1508                    .get(index)
1509                    .map(|&b| Value::Int(b as i64))
1510                    .ok_or_else(|| {
1511                        IonError::runtime(
1512                            ion_str!("bytes index out of bounds").to_string(),
1513                            span.line,
1514                            span.col,
1515                        )
1516                        .into()
1517                    })
1518            }
1519            (Value::Str(s), Value::Int(i)) => {
1520                let index = if *i < 0 {
1521                    s.chars().count() as i64 + i
1522                } else {
1523                    *i
1524                } as usize;
1525                s.chars()
1526                    .nth(index)
1527                    .map(|c| Value::Str(c.to_string()))
1528                    .ok_or_else(|| {
1529                        IonError::runtime(
1530                            ion_str!("string index out of bounds").to_string(),
1531                            span.line,
1532                            span.col,
1533                        )
1534                        .into()
1535                    })
1536            }
1537            (Value::Tuple(items), Value::Int(i)) => {
1538                let index = *i as usize;
1539                items.get(index).cloned().ok_or_else(|| {
1540                    IonError::runtime(
1541                        ion_str!("tuple index out of bounds").to_string(),
1542                        span.line,
1543                        span.col,
1544                    )
1545                    .into()
1546                })
1547            }
1548            _ => Err(IonError::type_err(
1549                format!(
1550                    "{}{}{}{}",
1551                    ion_str!("cannot index "),
1552                    val.type_name(),
1553                    ion_str!(" with "),
1554                    idx.type_name(),
1555                ),
1556                span.line,
1557                span.col,
1558            )
1559            .into()),
1560        }
1561    }
1562
1563    fn slice_access(
1564        &self,
1565        val: &Value,
1566        start: Option<&Value>,
1567        end: Option<&Value>,
1568        inclusive: bool,
1569        span: Span,
1570    ) -> SignalResult {
1571        let get_idx = |v: Option<&Value>, default: i64| -> Result<i64, SignalOrError> {
1572            match v {
1573                Some(Value::Int(n)) => Ok(*n),
1574                None => Ok(default),
1575                Some(other) => Err(IonError::type_err(
1576                    format!(
1577                        "{}{}",
1578                        ion_str!("slice index must be int, got "),
1579                        other.type_name()
1580                    ),
1581                    span.line,
1582                    span.col,
1583                )
1584                .into()),
1585            }
1586        };
1587
1588        match val {
1589            Value::List(items) => {
1590                let len = items.len() as i64;
1591                let s = get_idx(start, 0)?;
1592                let e = get_idx(end, len)?;
1593                let s = s.max(0).min(len) as usize;
1594                let e = if inclusive {
1595                    (e + 1).max(0).min(len) as usize
1596                } else {
1597                    e.max(0).min(len) as usize
1598                };
1599                Ok(Value::List(items[s..e].to_vec()))
1600            }
1601            Value::Str(string) => {
1602                let chars: Vec<char> = string.chars().collect();
1603                let len = chars.len() as i64;
1604                let s = get_idx(start, 0)?;
1605                let e = get_idx(end, len)?;
1606                let s = s.max(0).min(len) as usize;
1607                let e = if inclusive {
1608                    (e + 1).max(0).min(len) as usize
1609                } else {
1610                    e.max(0).min(len) as usize
1611                };
1612                Ok(Value::Str(chars[s..e].iter().collect()))
1613            }
1614            Value::Bytes(bytes) => {
1615                let len = bytes.len() as i64;
1616                let s = get_idx(start, 0)?;
1617                let e = get_idx(end, len)?;
1618                let s = s.max(0).min(len) as usize;
1619                let e = if inclusive {
1620                    (e + 1).max(0).min(len) as usize
1621                } else {
1622                    e.max(0).min(len) as usize
1623                };
1624                Ok(Value::Bytes(bytes[s..e].to_vec()))
1625            }
1626            _ => Err(IonError::type_err(
1627                format!("{}{}", ion_str!("cannot slice "), val.type_name()),
1628                span.line,
1629                span.col,
1630            )
1631            .into()),
1632        }
1633    }
1634
1635    fn method_call(
1636        &mut self,
1637        receiver: &Value,
1638        method: &str,
1639        args: &[Value],
1640        span: Span,
1641    ) -> SignalResult {
1642        // Universal methods available on all types
1643        if method == "to_string" {
1644            return Ok(Value::Str(format!("{}", receiver)));
1645        }
1646        match receiver {
1647            Value::List(items) => self.list_method(items, method, args, span),
1648            Value::Tuple(items) => self.tuple_method(items, method, args, span),
1649            Value::Str(s) => self.string_method(s, method, args, span),
1650            Value::Bytes(b) => self.bytes_method(b, method, args, span),
1651            Value::Dict(map) => match method {
1652                "map" => {
1653                    let func = &args[0];
1654                    let mut result = indexmap::IndexMap::new();
1655                    for (k, v) in map {
1656                        let mapped =
1657                            self.call_value(func, &[Value::Str(k.clone()), v.clone()], span)?;
1658                        result.insert(k.clone(), mapped);
1659                    }
1660                    Ok(Value::Dict(result))
1661                }
1662                "filter" => {
1663                    let func = &args[0];
1664                    let mut result = indexmap::IndexMap::new();
1665                    for (k, v) in map {
1666                        let keep =
1667                            self.call_value(func, &[Value::Str(k.clone()), v.clone()], span)?;
1668                        if keep.is_truthy() {
1669                            result.insert(k.clone(), v.clone());
1670                        }
1671                    }
1672                    Ok(Value::Dict(result))
1673                }
1674                _ => self.dict_method(map, method, args, span),
1675            },
1676            Value::Set(items) => self.set_method(items, method, args, span),
1677            Value::Option(opt) => self.option_method(opt.clone(), method, args, span),
1678            Value::Result(res) => self.result_method(res.clone(), method, args, span),
1679            Value::Range {
1680                start,
1681                end,
1682                inclusive,
1683            } => match method {
1684                "len" => Ok(Value::Int(Value::range_len(*start, *end, *inclusive))),
1685                "contains" => {
1686                    let val = args[0]
1687                        .as_int()
1688                        .ok_or_else(|| {
1689                            IonError::type_err(
1690                                ion_str!("range.contains requires int"),
1691                                span.line,
1692                                span.col,
1693                            )
1694                        })
1695                        .map_err(SignalOrError::from)?;
1696                    let in_range = if *inclusive {
1697                        val >= *start && val <= *end
1698                    } else {
1699                        val >= *start && val < *end
1700                    };
1701                    Ok(Value::Bool(in_range))
1702                }
1703                "to_list" => Ok(Value::List(Value::range_to_list(*start, *end, *inclusive))),
1704                // For other list-like methods, materialize and delegate
1705                _ => {
1706                    let items = Value::range_to_list(*start, *end, *inclusive);
1707                    self.list_method(&items, method, args, span)
1708                }
1709            },
1710            Value::Cell(cell) => self.cell_method(cell, method, args, span),
1711            #[cfg(feature = "concurrency")]
1712            Value::Task(handle) => self.task_method(handle, method, args, span),
1713            #[cfg(feature = "concurrency")]
1714            Value::Channel(ch) => self.channel_method(ch, method, args, span),
1715            _ => Err(IonError::type_err(
1716                format!(
1717                    "{}{}{}{}",
1718                    ion_str!("no method '"),
1719                    method,
1720                    ion_str!("' on "),
1721                    receiver.type_name(),
1722                ),
1723                span.line,
1724                span.col,
1725            )
1726            .into()),
1727        }
1728    }
1729
1730    fn list_method(
1731        &mut self,
1732        items: &[Value],
1733        method: &str,
1734        args: &[Value],
1735        span: Span,
1736    ) -> SignalResult {
1737        match method {
1738            "len" => Ok(Value::Int(items.len() as i64)),
1739            "push" => {
1740                let mut new_list = items.to_vec();
1741                new_list.push(args[0].clone());
1742                Ok(Value::List(new_list))
1743            }
1744            "pop" => {
1745                if items.is_empty() {
1746                    Ok(Value::Tuple(vec![Value::List(vec![]), Value::Option(None)]))
1747                } else {
1748                    let mut new_list = items.to_vec();
1749                    let popped = new_list.pop().unwrap();
1750                    Ok(Value::Tuple(vec![
1751                        Value::List(new_list),
1752                        Value::Option(Some(Box::new(popped))),
1753                    ]))
1754                }
1755            }
1756            "map" => {
1757                let func = &args[0];
1758                let mut result = Vec::new();
1759                for item in items {
1760                    result.push(self.call_value(func, std::slice::from_ref(item), span)?);
1761                }
1762                Ok(Value::List(result))
1763            }
1764            "filter" => {
1765                let func = &args[0];
1766                let mut result = Vec::new();
1767                for item in items {
1768                    let keep = self.call_value(func, std::slice::from_ref(item), span)?;
1769                    if keep.is_truthy() {
1770                        result.push(item.clone());
1771                    }
1772                }
1773                Ok(Value::List(result))
1774            }
1775            "fold" => {
1776                let mut acc = args[0].clone();
1777                let func = &args[1];
1778                for item in items {
1779                    acc = self.call_value(func, &[acc, item.clone()], span)?;
1780                }
1781                Ok(acc)
1782            }
1783            "flat_map" => {
1784                let func = &args[0];
1785                let mut result = Vec::new();
1786                for item in items {
1787                    let mapped = self.call_value(func, std::slice::from_ref(item), span)?;
1788                    match mapped {
1789                        Value::List(sub) => result.extend(sub),
1790                        other => result.push(other),
1791                    }
1792                }
1793                Ok(Value::List(result))
1794            }
1795            "any" => {
1796                let func = &args[0];
1797                for item in items {
1798                    let v = self.call_value(func, std::slice::from_ref(item), span)?;
1799                    if v.is_truthy() {
1800                        return Ok(Value::Bool(true));
1801                    }
1802                }
1803                Ok(Value::Bool(false))
1804            }
1805            "all" => {
1806                let func = &args[0];
1807                for item in items {
1808                    let v = self.call_value(func, std::slice::from_ref(item), span)?;
1809                    if !v.is_truthy() {
1810                        return Ok(Value::Bool(false));
1811                    }
1812                }
1813                Ok(Value::Bool(true))
1814            }
1815            "first" => Ok(match items.first() {
1816                Some(v) => Value::Option(Some(Box::new(v.clone()))),
1817                None => Value::Option(None),
1818            }),
1819            "last" => Ok(match items.last() {
1820                Some(v) => Value::Option(Some(Box::new(v.clone()))),
1821                None => Value::Option(None),
1822            }),
1823            "reverse" => {
1824                let mut rev = items.to_vec();
1825                rev.reverse();
1826                Ok(Value::List(rev))
1827            }
1828            "sort" => {
1829                if !items.is_empty() {
1830                    let first_type = std::mem::discriminant(&items[0]);
1831                    for item in items.iter().skip(1) {
1832                        if std::mem::discriminant(item) != first_type {
1833                            return Err(IonError::type_err(
1834                                ion_str!("sort() requires all elements to be the same type")
1835                                    .to_string(),
1836                                span.line,
1837                                span.col,
1838                            )
1839                            .into());
1840                        }
1841                    }
1842                }
1843                let mut sorted = items.to_vec();
1844                sorted.sort_by(|a, b| match (a, b) {
1845                    (Value::Int(x), Value::Int(y)) => x.cmp(y),
1846                    (Value::Float(x), Value::Float(y)) => {
1847                        x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal)
1848                    }
1849                    (Value::Str(x), Value::Str(y)) => x.cmp(y),
1850                    _ => std::cmp::Ordering::Equal,
1851                });
1852                Ok(Value::List(sorted))
1853            }
1854            "sort_by" => {
1855                let func = &args[0];
1856                let mut sorted = items.to_vec();
1857                let mut err: Option<SignalOrError> = None;
1858                let func_clone = func.clone();
1859                sorted.sort_by(|a, b| {
1860                    if err.is_some() {
1861                        return std::cmp::Ordering::Equal;
1862                    }
1863                    match self.call_value(&func_clone, &[a.clone(), b.clone()], span) {
1864                        Ok(Value::Int(n)) => {
1865                            if n < 0 {
1866                                std::cmp::Ordering::Less
1867                            } else if n > 0 {
1868                                std::cmp::Ordering::Greater
1869                            } else {
1870                                std::cmp::Ordering::Equal
1871                            }
1872                        }
1873                        Ok(_) => {
1874                            err = Some(
1875                                IonError::type_err(
1876                                    ion_str!("sort_by comparator must return an int").to_string(),
1877                                    span.line,
1878                                    span.col,
1879                                )
1880                                .into(),
1881                            );
1882                            std::cmp::Ordering::Equal
1883                        }
1884                        Err(e) => {
1885                            err = Some(e);
1886                            std::cmp::Ordering::Equal
1887                        }
1888                    }
1889                });
1890                if let Some(e) = err {
1891                    return Err(e);
1892                }
1893                Ok(Value::List(sorted))
1894            }
1895            "flatten" => {
1896                let mut result = Vec::new();
1897                for item in items {
1898                    if let Value::List(inner) = item {
1899                        result.extend(inner.iter().cloned());
1900                    } else {
1901                        result.push(item.clone());
1902                    }
1903                }
1904                Ok(Value::List(result))
1905            }
1906            "zip" => {
1907                if let Value::List(other) = &args[0] {
1908                    let result: Vec<Value> = items
1909                        .iter()
1910                        .zip(other.iter())
1911                        .map(|(a, b)| Value::Tuple(vec![a.clone(), b.clone()]))
1912                        .collect();
1913                    Ok(Value::List(result))
1914                } else {
1915                    Err(IonError::type_err(
1916                        ion_str!("zip requires a list argument").to_string(),
1917                        span.line,
1918                        span.col,
1919                    )
1920                    .into())
1921                }
1922            }
1923            "contains" => {
1924                let target = &args[0];
1925                Ok(Value::Bool(items.iter().any(|v| v == target)))
1926            }
1927            "join" => {
1928                let sep = if args.is_empty() {
1929                    String::new()
1930                } else {
1931                    args[0]
1932                        .as_str()
1933                        .ok_or_else(|| {
1934                            IonError::type_err(
1935                                ion_str!("join separator must be a string").to_string(),
1936                                span.line,
1937                                span.col,
1938                            )
1939                        })?
1940                        .to_string()
1941                };
1942                let parts: Vec<String> = items.iter().map(|v| v.to_string()).collect();
1943                Ok(Value::Str(parts.join(&sep)))
1944            }
1945            "enumerate" => Ok(Value::List(
1946                items
1947                    .iter()
1948                    .enumerate()
1949                    .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v.clone()]))
1950                    .collect(),
1951            )),
1952            "is_empty" => Ok(Value::Bool(items.is_empty())),
1953            "index" => {
1954                let target = &args[0];
1955                Ok(match items.iter().position(|v| v == target) {
1956                    Some(i) => Value::Option(Some(Box::new(Value::Int(i as i64)))),
1957                    None => Value::Option(None),
1958                })
1959            }
1960            "count" => {
1961                let target = &args[0];
1962                Ok(Value::Int(
1963                    items.iter().filter(|v| *v == target).count() as i64
1964                ))
1965            }
1966            "slice" => {
1967                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
1968                let end = args
1969                    .get(1)
1970                    .and_then(|a| a.as_int())
1971                    .map(|n| n as usize)
1972                    .unwrap_or(items.len());
1973                let start = start.min(items.len());
1974                let end = end.min(items.len());
1975                Ok(Value::List(items[start..end].to_vec()))
1976            }
1977            "dedup" => {
1978                let mut result: Vec<Value> = Vec::new();
1979                for item in items {
1980                    if result.last() != Some(item) {
1981                        result.push(item.clone());
1982                    }
1983                }
1984                Ok(Value::List(result))
1985            }
1986            "unique" => {
1987                let mut seen = Vec::new();
1988                let mut result = Vec::new();
1989                for item in items {
1990                    if !seen.contains(item) {
1991                        seen.push(item.clone());
1992                        result.push(item.clone());
1993                    }
1994                }
1995                Ok(Value::List(result))
1996            }
1997            "min" => {
1998                if items.is_empty() {
1999                    return Ok(Value::Option(None));
2000                }
2001                let mut min = &items[0];
2002                for item in items.iter().skip(1) {
2003                    match (min, item) {
2004                        (Value::Int(a), Value::Int(b)) => {
2005                            if b < a {
2006                                min = item;
2007                            }
2008                        }
2009                        (Value::Float(a), Value::Float(b)) => {
2010                            if b < a {
2011                                min = item;
2012                            }
2013                        }
2014                        (Value::Str(a), Value::Str(b)) => {
2015                            if b < a {
2016                                min = item;
2017                            }
2018                        }
2019                        _ => {
2020                            return Err(IonError::type_err(
2021                                ion_str!("min() requires homogeneous comparable elements")
2022                                    .to_string(),
2023                                span.line,
2024                                span.col,
2025                            )
2026                            .into())
2027                        }
2028                    }
2029                }
2030                Ok(Value::Option(Some(Box::new(min.clone()))))
2031            }
2032            "max" => {
2033                if items.is_empty() {
2034                    return Ok(Value::Option(None));
2035                }
2036                let mut max = &items[0];
2037                for item in items.iter().skip(1) {
2038                    match (max, item) {
2039                        (Value::Int(a), Value::Int(b)) => {
2040                            if b > a {
2041                                max = item;
2042                            }
2043                        }
2044                        (Value::Float(a), Value::Float(b)) => {
2045                            if b > a {
2046                                max = item;
2047                            }
2048                        }
2049                        (Value::Str(a), Value::Str(b)) => {
2050                            if b > a {
2051                                max = item;
2052                            }
2053                        }
2054                        _ => {
2055                            return Err(IonError::type_err(
2056                                ion_str!("max() requires homogeneous comparable elements")
2057                                    .to_string(),
2058                                span.line,
2059                                span.col,
2060                            )
2061                            .into())
2062                        }
2063                    }
2064                }
2065                Ok(Value::Option(Some(Box::new(max.clone()))))
2066            }
2067            "sum" => {
2068                let mut int_sum: i64 = 0;
2069                let mut float_sum: f64 = 0.0;
2070                let mut has_float = false;
2071                for item in items {
2072                    match item {
2073                        Value::Int(n) => int_sum += n,
2074                        Value::Float(f) => {
2075                            has_float = true;
2076                            float_sum += f;
2077                        }
2078                        _ => {
2079                            return Err(IonError::type_err(
2080                                ion_str!("sum() requires numeric elements").to_string(),
2081                                span.line,
2082                                span.col,
2083                            )
2084                            .into())
2085                        }
2086                    }
2087                }
2088                if has_float {
2089                    Ok(Value::Float(float_sum + int_sum as f64))
2090                } else {
2091                    Ok(Value::Int(int_sum))
2092                }
2093            }
2094            "window" => {
2095                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2096                    IonError::type_err(
2097                        ion_str!("window requires int argument").to_string(),
2098                        span.line,
2099                        span.col,
2100                    )
2101                })? as usize;
2102                if n == 0 {
2103                    return Err(IonError::runtime(
2104                        ion_str!("window size must be > 0").to_string(),
2105                        span.line,
2106                        span.col,
2107                    )
2108                    .into());
2109                }
2110                let result: Vec<Value> =
2111                    items.windows(n).map(|w| Value::List(w.to_vec())).collect();
2112                Ok(Value::List(result))
2113            }
2114            "chunk" => {
2115                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2116                    IonError::type_err(
2117                        ion_str!("chunk requires int argument").to_string(),
2118                        span.line,
2119                        span.col,
2120                    )
2121                })? as usize;
2122                if n == 0 {
2123                    return Err(IonError::type_err(
2124                        ion_str!("chunk size must be > 0").to_string(),
2125                        span.line,
2126                        span.col,
2127                    )
2128                    .into());
2129                }
2130                let result: Vec<Value> = items.chunks(n).map(|c| Value::List(c.to_vec())).collect();
2131                Ok(Value::List(result))
2132            }
2133            "reduce" => {
2134                if items.is_empty() {
2135                    return Err(IonError::type_err(
2136                        ion_str!("reduce on empty list").to_string(),
2137                        span.line,
2138                        span.col,
2139                    )
2140                    .into());
2141                }
2142                let func = &args[0];
2143                let mut acc = items[0].clone();
2144                for item in items.iter().skip(1) {
2145                    acc = self.call_value(func, &[acc, item.clone()], span)?;
2146                }
2147                Ok(acc)
2148            }
2149            _ => Err(IonError::type_err(
2150                format!(
2151                    "{}{}{}",
2152                    ion_str!("no method '"),
2153                    method,
2154                    ion_str!("' on list")
2155                ),
2156                span.line,
2157                span.col,
2158            )
2159            .into()),
2160        }
2161    }
2162
2163    fn set_method(
2164        &self,
2165        items: &[Value],
2166        method: &str,
2167        args: &[Value],
2168        span: Span,
2169    ) -> SignalResult {
2170        match method {
2171            "len" => Ok(Value::Int(items.len() as i64)),
2172            "contains" => {
2173                let target = &args[0];
2174                Ok(Value::Bool(items.iter().any(|v| v == target)))
2175            }
2176            "is_empty" => Ok(Value::Bool(items.is_empty())),
2177            "add" => {
2178                let val = &args[0];
2179                let mut new = items.to_vec();
2180                if !new.iter().any(|v| v == val) {
2181                    new.push(val.clone());
2182                }
2183                Ok(Value::Set(new))
2184            }
2185            "remove" => {
2186                let val = &args[0];
2187                let new: Vec<Value> = items.iter().filter(|v| *v != val).cloned().collect();
2188                Ok(Value::Set(new))
2189            }
2190            "union" => {
2191                if let Value::Set(other) = &args[0] {
2192                    let mut new = items.to_vec();
2193                    for v in other {
2194                        if !new.iter().any(|x| x == v) {
2195                            new.push(v.clone());
2196                        }
2197                    }
2198                    Ok(Value::Set(new))
2199                } else {
2200                    Err(IonError::type_err(
2201                        ion_str!("union requires a set argument").to_string(),
2202                        span.line,
2203                        span.col,
2204                    )
2205                    .into())
2206                }
2207            }
2208            "intersection" => {
2209                if let Value::Set(other) = &args[0] {
2210                    let new: Vec<Value> = items
2211                        .iter()
2212                        .filter(|v| other.iter().any(|x| x == *v))
2213                        .cloned()
2214                        .collect();
2215                    Ok(Value::Set(new))
2216                } else {
2217                    Err(IonError::type_err(
2218                        ion_str!("intersection requires a set argument").to_string(),
2219                        span.line,
2220                        span.col,
2221                    )
2222                    .into())
2223                }
2224            }
2225            "difference" => {
2226                if let Value::Set(other) = &args[0] {
2227                    let new: Vec<Value> = items
2228                        .iter()
2229                        .filter(|v| !other.iter().any(|x| x == *v))
2230                        .cloned()
2231                        .collect();
2232                    Ok(Value::Set(new))
2233                } else {
2234                    Err(IonError::type_err(
2235                        ion_str!("difference requires a set argument").to_string(),
2236                        span.line,
2237                        span.col,
2238                    )
2239                    .into())
2240                }
2241            }
2242            "to_list" => Ok(Value::List(items.to_vec())),
2243            _ => Err(IonError::type_err(
2244                format!(
2245                    "{}{}{}",
2246                    ion_str!("no method '"),
2247                    method,
2248                    ion_str!("' on set")
2249                ),
2250                span.line,
2251                span.col,
2252            )
2253            .into()),
2254        }
2255    }
2256
2257    fn cell_method(
2258        &mut self,
2259        cell: &std::sync::Arc<std::sync::Mutex<Value>>,
2260        method: &str,
2261        args: &[Value],
2262        span: Span,
2263    ) -> SignalResult {
2264        match method {
2265            "get" => {
2266                let inner = cell.lock().unwrap();
2267                Ok(inner.clone())
2268            }
2269            "set" => {
2270                if args.is_empty() {
2271                    return Err(IonError::runtime(
2272                        ion_str!("cell.set() requires 1 argument").to_string(),
2273                        span.line,
2274                        span.col,
2275                    )
2276                    .into());
2277                }
2278                let mut inner = cell.lock().unwrap();
2279                *inner = args[0].clone();
2280                Ok(Value::Unit)
2281            }
2282            "update" => {
2283                if args.is_empty() {
2284                    return Err(IonError::runtime(
2285                        ion_str!("cell.update() requires a function argument").to_string(),
2286                        span.line,
2287                        span.col,
2288                    )
2289                    .into());
2290                }
2291                let current = { cell.lock().unwrap().clone() };
2292                let new_val = self.call_value(&args[0], &[current], span)?;
2293                let mut inner = cell.lock().unwrap();
2294                *inner = new_val.clone();
2295                Ok(new_val)
2296            }
2297            _ => Err(IonError::type_err(
2298                format!(
2299                    "{}{}{}",
2300                    ion_str!("no method '"),
2301                    method,
2302                    ion_str!("' on cell"),
2303                ),
2304                span.line,
2305                span.col,
2306            )
2307            .into()),
2308        }
2309    }
2310
2311    fn tuple_method(
2312        &self,
2313        items: &[Value],
2314        method: &str,
2315        args: &[Value],
2316        span: Span,
2317    ) -> SignalResult {
2318        match method {
2319            "len" => Ok(Value::Int(items.len() as i64)),
2320            "contains" => {
2321                let target = &args[0];
2322                Ok(Value::Bool(items.iter().any(|v| v == target)))
2323            }
2324            "to_list" => Ok(Value::List(items.to_vec())),
2325            _ => Err(IonError::type_err(
2326                format!(
2327                    "{}{}{}",
2328                    ion_str!("no method '"),
2329                    method,
2330                    ion_str!("' on tuple")
2331                ),
2332                span.line,
2333                span.col,
2334            )
2335            .into()),
2336        }
2337    }
2338
2339    fn string_method(&self, s: &str, method: &str, args: &[Value], span: Span) -> SignalResult {
2340        match method {
2341            "len" => Ok(Value::Int(s.len() as i64)),
2342            "contains" => match &args[0] {
2343                Value::Str(sub) => Ok(Value::Bool(s.contains(sub.as_str()))),
2344                Value::Int(code) => {
2345                    let ch = char::from_u32(*code as u32).ok_or_else(|| {
2346                        IonError::type_err(
2347                            ion_str!("invalid char code").to_string(),
2348                            span.line,
2349                            span.col,
2350                        )
2351                    })?;
2352                    Ok(Value::Bool(s.contains(ch)))
2353                }
2354                _ => Err(IonError::type_err(
2355                    ion_str!("contains requires string or int argument").to_string(),
2356                    span.line,
2357                    span.col,
2358                )
2359                .into()),
2360            },
2361            "starts_with" => {
2362                let sub = args[0].as_str().ok_or_else(|| {
2363                    IonError::type_err(
2364                        ion_str!("starts_with requires string argument").to_string(),
2365                        span.line,
2366                        span.col,
2367                    )
2368                })?;
2369                Ok(Value::Bool(s.starts_with(sub)))
2370            }
2371            "ends_with" => {
2372                let sub = args[0].as_str().ok_or_else(|| {
2373                    IonError::type_err(
2374                        ion_str!("ends_with requires string argument").to_string(),
2375                        span.line,
2376                        span.col,
2377                    )
2378                })?;
2379                Ok(Value::Bool(s.ends_with(sub)))
2380            }
2381            "trim" => Ok(Value::Str(s.trim().to_string())),
2382            "to_upper" => Ok(Value::Str(s.to_uppercase())),
2383            "to_lower" => Ok(Value::Str(s.to_lowercase())),
2384            "split" => {
2385                let delim = args[0].as_str().ok_or_else(|| {
2386                    IonError::type_err(
2387                        ion_str!("split requires string argument").to_string(),
2388                        span.line,
2389                        span.col,
2390                    )
2391                })?;
2392                let parts: Vec<Value> = s.split(delim).map(|p| Value::Str(p.to_string())).collect();
2393                Ok(Value::List(parts))
2394            }
2395            "replace" => {
2396                let from = args[0].as_str().ok_or_else(|| {
2397                    IonError::type_err(
2398                        ion_str!("replace requires string arguments").to_string(),
2399                        span.line,
2400                        span.col,
2401                    )
2402                })?;
2403                let to = args[1].as_str().ok_or_else(|| {
2404                    IonError::type_err(
2405                        ion_str!("replace requires string arguments").to_string(),
2406                        span.line,
2407                        span.col,
2408                    )
2409                })?;
2410                Ok(Value::Str(s.replace(from, to)))
2411            }
2412            "chars" => {
2413                let chars: Vec<Value> = s.chars().map(|c| Value::Str(c.to_string())).collect();
2414                Ok(Value::List(chars))
2415            }
2416            "char_len" => Ok(Value::Int(s.chars().count() as i64)),
2417            "is_empty" => Ok(Value::Bool(s.is_empty())),
2418            "trim_start" => Ok(Value::Str(s.trim_start().to_string())),
2419            "trim_end" => Ok(Value::Str(s.trim_end().to_string())),
2420            "repeat" => {
2421                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2422                    IonError::type_err(
2423                        ion_str!("repeat requires int argument").to_string(),
2424                        span.line,
2425                        span.col,
2426                    )
2427                })?;
2428                Ok(Value::Str(s.repeat(n as usize)))
2429            }
2430            "find" => {
2431                let sub = args[0].as_str().ok_or_else(|| {
2432                    IonError::type_err(
2433                        ion_str!("find requires string argument").to_string(),
2434                        span.line,
2435                        span.col,
2436                    )
2437                })?;
2438                Ok(match s.find(sub) {
2439                    Some(byte_idx) => {
2440                        let char_idx = s[..byte_idx].chars().count();
2441                        Value::Option(Some(Box::new(Value::Int(char_idx as i64))))
2442                    }
2443                    None => Value::Option(None),
2444                })
2445            }
2446            "to_int" => Ok(match s.trim().parse::<i64>() {
2447                std::result::Result::Ok(n) => Value::Result(Ok(Box::new(Value::Int(n)))),
2448                std::result::Result::Err(e) => {
2449                    Value::Result(Err(Box::new(Value::Str(e.to_string()))))
2450                }
2451            }),
2452            "to_float" => Ok(match s.trim().parse::<f64>() {
2453                std::result::Result::Ok(f) => Value::Result(Ok(Box::new(Value::Float(f)))),
2454                std::result::Result::Err(e) => {
2455                    Value::Result(Err(Box::new(Value::Str(e.to_string()))))
2456                }
2457            }),
2458            "bytes" => {
2459                let bytes: Vec<Value> = s.bytes().map(|b| Value::Int(b as i64)).collect();
2460                Ok(Value::List(bytes))
2461            }
2462            "strip_prefix" => {
2463                let pre = args[0].as_str().ok_or_else(|| {
2464                    IonError::type_err(
2465                        ion_str!("strip_prefix requires string argument").to_string(),
2466                        span.line,
2467                        span.col,
2468                    )
2469                })?;
2470                Ok(Value::Str(s.strip_prefix(pre).unwrap_or(s).to_string()))
2471            }
2472            "strip_suffix" => {
2473                let suf = args[0].as_str().ok_or_else(|| {
2474                    IonError::type_err(
2475                        ion_str!("strip_suffix requires string argument").to_string(),
2476                        span.line,
2477                        span.col,
2478                    )
2479                })?;
2480                Ok(Value::Str(s.strip_suffix(suf).unwrap_or(s).to_string()))
2481            }
2482            "pad_start" => {
2483                let width = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2484                    IonError::type_err(
2485                        ion_str!("pad_start requires int argument").to_string(),
2486                        span.line,
2487                        span.col,
2488                    )
2489                })? as usize;
2490                let ch = args
2491                    .get(1)
2492                    .and_then(|a| a.as_str())
2493                    .and_then(|s| s.chars().next())
2494                    .unwrap_or(' ');
2495                let char_len = s.chars().count();
2496                if char_len >= width {
2497                    Ok(Value::Str(s.to_string()))
2498                } else {
2499                    let pad: String = std::iter::repeat_n(ch, width - char_len).collect();
2500                    Ok(Value::Str(format!("{}{}", pad, s)))
2501                }
2502            }
2503            "pad_end" => {
2504                let width = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2505                    IonError::type_err(
2506                        ion_str!("pad_end requires int argument").to_string(),
2507                        span.line,
2508                        span.col,
2509                    )
2510                })? as usize;
2511                let ch = args
2512                    .get(1)
2513                    .and_then(|a| a.as_str())
2514                    .and_then(|s| s.chars().next())
2515                    .unwrap_or(' ');
2516                let char_len = s.chars().count();
2517                if char_len >= width {
2518                    Ok(Value::Str(s.to_string()))
2519                } else {
2520                    let pad: String = std::iter::repeat_n(ch, width - char_len).collect();
2521                    Ok(Value::Str(format!("{}{}", s, pad)))
2522                }
2523            }
2524            "reverse" => Ok(Value::Str(s.chars().rev().collect())),
2525            "slice" => {
2526                let chars: Vec<char> = s.chars().collect();
2527                let char_count = chars.len();
2528                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
2529                let end = args
2530                    .get(1)
2531                    .and_then(|a| a.as_int())
2532                    .map(|n| n as usize)
2533                    .unwrap_or(char_count);
2534                let start = start.min(char_count);
2535                let end = end.min(char_count);
2536                Ok(Value::Str(chars[start..end].iter().collect()))
2537            }
2538            _ => Err(IonError::type_err(
2539                format!(
2540                    "{}{}{}",
2541                    ion_str!("no method '"),
2542                    method,
2543                    ion_str!("' on string")
2544                ),
2545                span.line,
2546                span.col,
2547            )
2548            .into()),
2549        }
2550    }
2551
2552    fn bytes_method(&self, bytes: &[u8], method: &str, args: &[Value], span: Span) -> SignalResult {
2553        match method {
2554            "len" => Ok(Value::Int(bytes.len() as i64)),
2555            "is_empty" => Ok(Value::Bool(bytes.is_empty())),
2556            "contains" => {
2557                let byte = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2558                    IonError::type_err(
2559                        ion_str!("bytes.contains() requires an int argument").to_string(),
2560                        span.line,
2561                        span.col,
2562                    )
2563                })?;
2564                Ok(Value::Bool(bytes.contains(&(byte as u8))))
2565            }
2566            "slice" => {
2567                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
2568                let end = args
2569                    .get(1)
2570                    .and_then(|a| a.as_int())
2571                    .map(|n| n as usize)
2572                    .unwrap_or(bytes.len());
2573                let start = start.min(bytes.len());
2574                let end = end.min(bytes.len());
2575                Ok(Value::Bytes(bytes[start..end].to_vec()))
2576            }
2577            "to_list" => Ok(Value::List(
2578                bytes.iter().map(|&b| Value::Int(b as i64)).collect(),
2579            )),
2580            "to_str" => match std::str::from_utf8(bytes) {
2581                std::result::Result::Ok(s) => {
2582                    Ok(Value::Result(Ok(Box::new(Value::Str(s.to_string())))))
2583                }
2584                std::result::Result::Err(e) => {
2585                    Ok(Value::Result(Err(Box::new(Value::Str(format!("{}", e))))))
2586                }
2587            },
2588            "to_hex" => {
2589                let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
2590                Ok(Value::Str(hex))
2591            }
2592            "find" => {
2593                let needle = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2594                    IonError::type_err(
2595                        ion_str!("bytes.find() requires an int argument").to_string(),
2596                        span.line,
2597                        span.col,
2598                    )
2599                })?;
2600                let pos = bytes.iter().position(|&b| b == needle as u8);
2601                Ok(match pos {
2602                    Some(i) => Value::Option(Some(Box::new(Value::Int(i as i64)))),
2603                    None => Value::Option(None),
2604                })
2605            }
2606            "reverse" => {
2607                let mut rev = bytes.to_vec();
2608                rev.reverse();
2609                Ok(Value::Bytes(rev))
2610            }
2611            "push" => {
2612                let byte = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2613                    IonError::type_err(
2614                        ion_str!("bytes.push() requires an int argument").to_string(),
2615                        span.line,
2616                        span.col,
2617                    )
2618                })?;
2619                let mut new = bytes.to_vec();
2620                new.push(byte as u8);
2621                Ok(Value::Bytes(new))
2622            }
2623            _ => Err(IonError::type_err(
2624                format!(
2625                    "{}{}{}{}",
2626                    ion_str!("no method '"),
2627                    method,
2628                    ion_str!("' on "),
2629                    ion_str!("bytes"),
2630                ),
2631                span.line,
2632                span.col,
2633            )
2634            .into()),
2635        }
2636    }
2637
2638    fn dict_method(
2639        &self,
2640        map: &IndexMap<String, Value>,
2641        method: &str,
2642        args: &[Value],
2643        span: Span,
2644    ) -> SignalResult {
2645        match method {
2646            "len" => Ok(Value::Int(map.len() as i64)),
2647            "keys" => Ok(Value::List(
2648                map.keys().map(|k| Value::Str(k.clone())).collect(),
2649            )),
2650            "values" => Ok(Value::List(map.values().cloned().collect())),
2651            "entries" => Ok(Value::List(
2652                map.iter()
2653                    .map(|(k, v)| Value::Tuple(vec![Value::Str(k.clone()), v.clone()]))
2654                    .collect(),
2655            )),
2656            "contains_key" => {
2657                let key = args[0].as_str().ok_or_else(|| {
2658                    IonError::type_err(
2659                        ion_str!("contains_key requires string argument").to_string(),
2660                        span.line,
2661                        span.col,
2662                    )
2663                })?;
2664                Ok(Value::Bool(map.contains_key(key)))
2665            }
2666            "get" => {
2667                let key = args[0].as_str().ok_or_else(|| {
2668                    IonError::type_err(
2669                        ion_str!("get requires string argument").to_string(),
2670                        span.line,
2671                        span.col,
2672                    )
2673                })?;
2674                Ok(match map.get(key) {
2675                    Some(v) => Value::Option(Some(Box::new(v.clone()))),
2676                    None => Value::Option(None),
2677                })
2678            }
2679            "insert" => {
2680                if args.len() < 2 {
2681                    return Err(IonError::runtime(
2682                        ion_str!("insert requires 2 arguments: key, value").to_string(),
2683                        span.line,
2684                        span.col,
2685                    )
2686                    .into());
2687                }
2688                let key = args[0].as_str().ok_or_else(|| {
2689                    IonError::type_err(
2690                        ion_str!("insert requires string key").to_string(),
2691                        span.line,
2692                        span.col,
2693                    )
2694                })?;
2695                let mut new_map = map.clone();
2696                new_map.insert(key.to_string(), args[1].clone());
2697                Ok(Value::Dict(new_map))
2698            }
2699            "remove" => {
2700                let key = args[0].as_str().ok_or_else(|| {
2701                    IonError::type_err(
2702                        ion_str!("remove requires string key").to_string(),
2703                        span.line,
2704                        span.col,
2705                    )
2706                })?;
2707                let mut new_map = map.clone();
2708                new_map.shift_remove(key);
2709                Ok(Value::Dict(new_map))
2710            }
2711            "merge" => {
2712                if let Value::Dict(other) = &args[0] {
2713                    let mut new_map = map.clone();
2714                    for (k, v) in other {
2715                        new_map.insert(k.clone(), v.clone());
2716                    }
2717                    Ok(Value::Dict(new_map))
2718                } else {
2719                    Err(IonError::type_err(
2720                        ion_str!("merge requires a dict argument").to_string(),
2721                        span.line,
2722                        span.col,
2723                    )
2724                    .into())
2725                }
2726            }
2727            "is_empty" => Ok(Value::Bool(map.is_empty())),
2728            "update" => {
2729                if let Value::Dict(other) = &args[0] {
2730                    let mut new_map = map.clone();
2731                    for (k, v) in other {
2732                        new_map.insert(k.clone(), v.clone());
2733                    }
2734                    Ok(Value::Dict(new_map))
2735                } else {
2736                    Err(IonError::type_err(
2737                        ion_str!("update requires a dict argument").to_string(),
2738                        span.line,
2739                        span.col,
2740                    )
2741                    .into())
2742                }
2743            }
2744            "keys_of" => {
2745                let target = &args[0];
2746                let keys: Vec<Value> = map
2747                    .iter()
2748                    .filter(|(_, v)| *v == target)
2749                    .map(|(k, _)| Value::Str(k.clone()))
2750                    .collect();
2751                Ok(Value::List(keys))
2752            }
2753            "zip" => {
2754                if let Value::Dict(other) = &args[0] {
2755                    let mut result = indexmap::IndexMap::new();
2756                    for (k, v) in map {
2757                        if let Some(ov) = other.get(k) {
2758                            result.insert(k.clone(), Value::Tuple(vec![v.clone(), ov.clone()]));
2759                        }
2760                    }
2761                    Ok(Value::Dict(result))
2762                } else {
2763                    Err(IonError::type_err(
2764                        ion_str!("zip requires a dict argument").to_string(),
2765                        span.line,
2766                        span.col,
2767                    )
2768                    .into())
2769                }
2770            }
2771            _ => Err(IonError::type_err(
2772                format!(
2773                    "{}{}{}",
2774                    ion_str!("no method '"),
2775                    method,
2776                    ion_str!("' on dict")
2777                ),
2778                span.line,
2779                span.col,
2780            )
2781            .into()),
2782        }
2783    }
2784
2785    fn option_method(
2786        &mut self,
2787        opt: Option<Box<Value>>,
2788        method: &str,
2789        args: &[Value],
2790        span: Span,
2791    ) -> SignalResult {
2792        match method {
2793            "is_some" => Ok(Value::Bool(opt.is_some())),
2794            "is_none" => Ok(Value::Bool(opt.is_none())),
2795            "unwrap" => match opt {
2796                Some(v) => Ok(*v),
2797                None => {
2798                    Err(
2799                        IonError::runtime(ion_str!("called unwrap on None"), span.line, span.col)
2800                            .into(),
2801                    )
2802                }
2803            },
2804            "unwrap_or" => match opt {
2805                Some(v) => Ok(*v),
2806                None => Ok(args[0].clone()),
2807            },
2808            "expect" => match opt {
2809                Some(v) => Ok(*v),
2810                None => {
2811                    let default_msg = ion_str!("expect failed");
2812                    let msg = args[0].as_str().unwrap_or(&default_msg);
2813                    Err(IonError::runtime(msg.to_string(), span.line, span.col).into())
2814                }
2815            },
2816            "map" => {
2817                let func = args[0].clone();
2818                match opt {
2819                    Some(v) => {
2820                        let result = self.call_value(&func, &[*v], span)?;
2821                        Ok(Value::Option(Some(Box::new(result))))
2822                    }
2823                    None => Ok(Value::Option(None)),
2824                }
2825            }
2826            "and_then" => {
2827                let func = args[0].clone();
2828                match opt {
2829                    Some(v) => self.call_value(&func, &[*v], span),
2830                    None => Ok(Value::Option(None)),
2831                }
2832            }
2833            "or_else" => {
2834                let func = args[0].clone();
2835                match opt {
2836                    Some(v) => Ok(Value::Option(Some(v))),
2837                    None => self.call_value(&func, &[], span),
2838                }
2839            }
2840            "unwrap_or_else" => {
2841                let func = args[0].clone();
2842                match opt {
2843                    Some(v) => Ok(*v),
2844                    None => self.call_value(&func, &[], span),
2845                }
2846            }
2847            _ => Err(IonError::type_err(
2848                format!(
2849                    "{}{}{}",
2850                    ion_str!("no method '"),
2851                    method,
2852                    ion_str!("' on Option")
2853                ),
2854                span.line,
2855                span.col,
2856            )
2857            .into()),
2858        }
2859    }
2860
2861    fn result_method(
2862        &mut self,
2863        res: Result<Box<Value>, Box<Value>>,
2864        method: &str,
2865        args: &[Value],
2866        span: Span,
2867    ) -> SignalResult {
2868        match method {
2869            "is_ok" => Ok(Value::Bool(res.is_ok())),
2870            "is_err" => Ok(Value::Bool(res.is_err())),
2871            "unwrap" => match res {
2872                Ok(v) => Ok(*v),
2873                Err(e) => Err(IonError::runtime(
2874                    format!("{}{}", ion_str!("called unwrap on Err: "), e),
2875                    span.line,
2876                    span.col,
2877                )
2878                .into()),
2879            },
2880            "unwrap_or" => match res {
2881                Ok(v) => Ok(*v),
2882                Err(_) => Ok(args[0].clone()),
2883            },
2884            "expect" => match res {
2885                Ok(v) => Ok(*v),
2886                Err(e) => {
2887                    let default_msg = ion_str!("expect failed");
2888                    let msg = args[0].as_str().unwrap_or(&default_msg);
2889                    Err(IonError::runtime(format!("{}: {}", msg, e), span.line, span.col).into())
2890                }
2891            },
2892            "map" => {
2893                let func = args[0].clone();
2894                match res {
2895                    Ok(v) => {
2896                        let result = self.call_value(&func, &[*v], span)?;
2897                        Ok(Value::Result(Ok(Box::new(result))))
2898                    }
2899                    Err(e) => Ok(Value::Result(Err(e))),
2900                }
2901            }
2902            "map_err" => {
2903                let func = args[0].clone();
2904                match res {
2905                    Ok(v) => Ok(Value::Result(Ok(v))),
2906                    Err(e) => {
2907                        let result = self.call_value(&func, &[*e], span)?;
2908                        Ok(Value::Result(Err(Box::new(result))))
2909                    }
2910                }
2911            }
2912            "and_then" => {
2913                let func = args[0].clone();
2914                match res {
2915                    Ok(v) => self.call_value(&func, &[*v], span),
2916                    Err(e) => Ok(Value::Result(Err(e))),
2917                }
2918            }
2919            "or_else" => {
2920                let func = args[0].clone();
2921                match res {
2922                    Ok(v) => Ok(Value::Result(Ok(v))),
2923                    Err(e) => self.call_value(&func, &[*e], span),
2924                }
2925            }
2926            "unwrap_or_else" => {
2927                let func = args[0].clone();
2928                match res {
2929                    Ok(v) => Ok(*v),
2930                    Err(e) => self.call_value(&func, &[*e], span),
2931                }
2932            }
2933            _ => Err(IonError::type_err(
2934                format!(
2935                    "{}{}{}",
2936                    ion_str!("no method '"),
2937                    method,
2938                    ion_str!("' on Result")
2939                ),
2940                span.line,
2941                span.col,
2942            )
2943            .into()),
2944        }
2945    }
2946
2947    fn call_value(&mut self, func: &Value, args: &[Value], span: Span) -> SignalResult {
2948        match func {
2949            Value::Fn(ion_fn) => {
2950                if self.call_depth >= self.limits.max_call_depth {
2951                    return Err(IonError::runtime(
2952                        ion_str!("maximum call depth exceeded").to_string(),
2953                        span.line,
2954                        span.col,
2955                    )
2956                    .into());
2957                }
2958                self.call_depth += 1;
2959                self.env.push_scope();
2960                let result = (|| {
2961                    // Load captures
2962                    for (name, val) in &ion_fn.captures {
2963                        self.env.define(name.clone(), val.clone(), false);
2964                    }
2965                    // Bind parameters
2966                    for (i, param) in ion_fn.params.iter().enumerate() {
2967                        let val = if i < args.len() {
2968                            args[i].clone()
2969                        } else if let Some(default) = &param.default {
2970                            self.eval_expr(default)?
2971                        } else {
2972                            return Err(IonError::runtime(
2973                                format!(
2974                                    "{}{}{}{}{}{}",
2975                                    ion_str!("function '"),
2976                                    ion_fn.name,
2977                                    ion_str!("' expected "),
2978                                    ion_fn.params.len(),
2979                                    ion_str!(" arguments, got "),
2980                                    args.len(),
2981                                ),
2982                                span.line,
2983                                span.col,
2984                            )
2985                            .into());
2986                        };
2987                        self.env.define(param.name.clone(), val, false);
2988                    }
2989                    self.eval_stmts(&ion_fn.body)
2990                })();
2991                self.env.pop_scope();
2992                self.call_depth -= 1;
2993                match result {
2994                    Ok(v) => Ok(v),
2995                    Err(SignalOrError::Signal(Signal::Return(v))) => Ok(v),
2996                    Err(SignalOrError::Signal(Signal::Break(_))) => Err(IonError::runtime(
2997                        ion_str!("break outside of loop").to_string(),
2998                        span.line,
2999                        span.col,
3000                    )
3001                    .into()),
3002                    Err(SignalOrError::Signal(Signal::Continue)) => Err(IonError::runtime(
3003                        ion_str!("continue outside of loop").to_string(),
3004                        span.line,
3005                        span.col,
3006                    )
3007                    .into()),
3008                    Err(SignalOrError::Error(e)) => {
3009                        // Convert ? propagation into values at function boundary
3010                        if e.kind == ErrorKind::PropagatedErr {
3011                            Ok(Value::Result(Err(Box::new(Value::Str(e.message.clone())))))
3012                        } else if e.kind == ErrorKind::PropagatedNone {
3013                            Ok(Value::Option(None))
3014                        } else {
3015                            Err(e.into())
3016                        }
3017                    }
3018                }
3019            }
3020            #[cfg(feature = "concurrency")]
3021            Value::BuiltinFn(ref name, _) if name == "timeout" => self.builtin_timeout(args, span),
3022            Value::BuiltinFn(_, func) => {
3023                func(args).map_err(|msg| IonError::runtime(msg, span.line, span.col).into())
3024            }
3025            Value::BuiltinClosure(_, func) => func
3026                .call(args)
3027                .map_err(|msg| IonError::runtime(msg, span.line, span.col).into()),
3028            _ => Err(IonError::type_err(
3029                format!("{}{}", ion_str!("not callable: "), func.type_name()),
3030                span.line,
3031                span.col,
3032            )
3033            .into()),
3034        }
3035    }
3036
3037    #[cfg(feature = "concurrency")]
3038    fn builtin_timeout(&mut self, args: &[Value], span: Span) -> SignalResult {
3039        if args.len() < 2 {
3040            return Err(IonError::runtime(
3041                ion_str!("timeout(ms, fn) requires 2 arguments").to_string(),
3042                span.line,
3043                span.col,
3044            )
3045            .into());
3046        }
3047        let ms = args[0].as_int().ok_or_else(|| {
3048            SignalOrError::Error(IonError::runtime(
3049                ion_str!("timeout: first argument must be int (ms)").to_string(),
3050                span.line,
3051                span.col,
3052            ))
3053        })?;
3054        let func = args[1].clone();
3055        // Call the function synchronously but with a timeout via spawn + join_timeout
3056        let captured_env = self.env.capture();
3057        let limits = self.limits.clone();
3058        let types = self.types.clone();
3059        let cancel_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
3060        let task = crate::async_rt::spawn_task_with_cancel(cancel_flag, move |flag| {
3061            let mut child = Interpreter::new();
3062            child.limits = limits;
3063            child.types = types;
3064            child.cancel_flag = Some(flag);
3065            for (name, val) in captured_env {
3066                child.env.define(name, val, false);
3067            }
3068            child
3069                .call_value(&func, &[], crate::ast::Span { line: 0, col: 0 })
3070                .map_err(|e| match e {
3071                    SignalOrError::Error(err) => err,
3072                    SignalOrError::Signal(_) => {
3073                        IonError::runtime(ion_str!("unexpected signal in timeout"), 0, 0)
3074                    }
3075                })
3076        });
3077        match task.join_timeout(std::time::Duration::from_millis(ms as u64)) {
3078            Some(Ok(val)) => Ok(Value::Option(Some(Box::new(val)))),
3079            Some(Err(e)) => Err(e.into()),
3080            None => {
3081                // Timer expired — signal cancellation so the runaway
3082                // task terminates at the next statement boundary.
3083                task.cancel();
3084                Ok(Value::Option(None))
3085            }
3086        }
3087    }
3088
3089    fn call_with_named(
3090        &mut self,
3091        func: &Value,
3092        named_args: Vec<(Option<String>, Value)>,
3093        span: Span,
3094    ) -> SignalResult {
3095        match func {
3096            Value::Fn(ion_fn) => {
3097                // Reorder named args to match parameter positions
3098                let mut ordered = vec![None; ion_fn.params.len()];
3099                let mut pos_idx = 0;
3100                for (name, val) in named_args {
3101                    if let Some(name) = name {
3102                        // Find param by name
3103                        let param_idx = ion_fn
3104                            .params
3105                            .iter()
3106                            .position(|p| p.name == name)
3107                            .ok_or_else(|| {
3108                                IonError::runtime(
3109                                    format!(
3110                                        "{}{}{}{}",
3111                                        ion_str!("unknown parameter '"),
3112                                        name,
3113                                        ion_str!("' for function '"),
3114                                        ion_fn.name,
3115                                    ),
3116                                    span.line,
3117                                    span.col,
3118                                )
3119                            })?;
3120                        ordered[param_idx] = Some(val);
3121                    } else {
3122                        // Positional arg — fill next empty slot
3123                        while pos_idx < ordered.len() && ordered[pos_idx].is_some() {
3124                            pos_idx += 1;
3125                        }
3126                        if pos_idx < ordered.len() {
3127                            ordered[pos_idx] = Some(val);
3128                            pos_idx += 1;
3129                        }
3130                    }
3131                }
3132                // Keep as Option<Value> so we can distinguish "not provided" from "provided Unit"
3133                let opt_args: Vec<Option<Value>> = ordered;
3134                // Use call_value with the reordered args, but handle defaults specially
3135                if self.call_depth >= self.limits.max_call_depth {
3136                    return Err(IonError::runtime(
3137                        ion_str!("maximum call depth exceeded").to_string(),
3138                        span.line,
3139                        span.col,
3140                    )
3141                    .into());
3142                }
3143                self.call_depth += 1;
3144                self.env.push_scope();
3145                let result = (|| {
3146                    for (name, val) in &ion_fn.captures {
3147                        self.env.define(name.clone(), val.clone(), false);
3148                    }
3149                    for (i, param) in ion_fn.params.iter().enumerate() {
3150                        let val = if i < opt_args.len() && opt_args[i].is_some() {
3151                            opt_args[i].clone().unwrap()
3152                        } else if let Some(default) = &param.default {
3153                            self.eval_expr(default)?
3154                        } else {
3155                            return Err(IonError::runtime(
3156                                format!(
3157                                    "{}{}{}",
3158                                    ion_str!("missing argument '"),
3159                                    param.name,
3160                                    ion_str!("'"),
3161                                ),
3162                                span.line,
3163                                span.col,
3164                            )
3165                            .into());
3166                        };
3167                        self.env.define(param.name.clone(), val, false);
3168                    }
3169                    self.eval_stmts(&ion_fn.body)
3170                })();
3171                self.env.pop_scope();
3172                self.call_depth -= 1;
3173                match result {
3174                    Ok(v) => Ok(v),
3175                    Err(SignalOrError::Signal(Signal::Return(v))) => Ok(v),
3176                    Err(SignalOrError::Error(e)) if e.kind == ErrorKind::PropagatedErr => {
3177                        Ok(Value::Result(Err(Box::new(Value::Str(e.message.clone())))))
3178                    }
3179                    Err(SignalOrError::Error(e)) if e.kind == ErrorKind::PropagatedNone => {
3180                        Ok(Value::Option(None))
3181                    }
3182                    Err(e) => Err(e),
3183                }
3184            }
3185            _ => {
3186                // For builtins, just pass positional values
3187                let args: Vec<Value> = named_args.into_iter().map(|(_, v)| v).collect();
3188                self.call_value(func, &args, span)
3189            }
3190        }
3191    }
3192
3193    fn check_type_ann(val: &Value, ann: &TypeAnn, span: Span) -> Result<(), SignalOrError> {
3194        let matches = match ann {
3195            TypeAnn::Simple(name) => match name.as_str() {
3196                "int" => matches!(val, Value::Int(_)),
3197                "float" => matches!(val, Value::Float(_)),
3198                "bool" => matches!(val, Value::Bool(_)),
3199                "string" => matches!(val, Value::Str(_)),
3200                "bytes" => matches!(val, Value::Bytes(_)),
3201                "list" => matches!(val, Value::List(_)),
3202                "dict" => matches!(val, Value::Dict(_)),
3203                "tuple" => matches!(val, Value::Tuple(_)),
3204                "set" => matches!(val, Value::Set(_)),
3205                "fn" => matches!(
3206                    val,
3207                    Value::Fn(_) | Value::BuiltinFn(_, _) | Value::BuiltinClosure(_, _)
3208                ),
3209                "cell" => matches!(val, Value::Cell(_)),
3210                "any" => true,
3211                _ => true, // unknown types pass (forward compatibility)
3212            },
3213            TypeAnn::Option(_) => matches!(val, Value::Option(_)),
3214            TypeAnn::Result(_, _) => matches!(val, Value::Result(_)),
3215            TypeAnn::List(_) => matches!(val, Value::List(_)),
3216            TypeAnn::Dict(_, _) => matches!(val, Value::Dict(_)),
3217        };
3218        if !matches {
3219            return Err(IonError::type_err(
3220                format!(
3221                    "{}{}{}{}",
3222                    ion_str!("type mismatch: expected "),
3223                    Self::type_ann_name(ann),
3224                    ion_str!(", got "),
3225                    val.type_name()
3226                ),
3227                span.line,
3228                span.col,
3229            )
3230            .into());
3231        }
3232        Ok(())
3233    }
3234
3235    fn type_ann_name(ann: &TypeAnn) -> String {
3236        match ann {
3237            TypeAnn::Simple(name) => name.clone(),
3238            TypeAnn::Option(inner) => format!("Option<{}>", Self::type_ann_name(inner)),
3239            TypeAnn::Result(ok, err) => {
3240                format!(
3241                    "Result<{}, {}>",
3242                    Self::type_ann_name(ok),
3243                    Self::type_ann_name(err)
3244                )
3245            }
3246            TypeAnn::List(inner) => format!("list<{}>", Self::type_ann_name(inner)),
3247            TypeAnn::Dict(k, v) => {
3248                format!(
3249                    "dict<{}, {}>",
3250                    Self::type_ann_name(k),
3251                    Self::type_ann_name(v)
3252                )
3253            }
3254        }
3255    }
3256
3257    fn value_to_iter(&self, val: &Value, span: Span) -> Result<Vec<Value>, SignalOrError> {
3258        match val {
3259            Value::List(items) => Ok(items.clone()),
3260            Value::Set(items) => Ok(items.clone()),
3261            Value::Tuple(items) => Ok(items.clone()),
3262            Value::Dict(map) => Ok(map
3263                .iter()
3264                .map(|(k, v)| Value::Tuple(vec![Value::Str(k.clone()), v.clone()]))
3265                .collect()),
3266            Value::Str(s) => Ok(s.chars().map(|c| Value::Str(c.to_string())).collect()),
3267            Value::Bytes(bytes) => Ok(bytes.iter().map(|&b| Value::Int(b as i64)).collect()),
3268            Value::Range {
3269                start,
3270                end,
3271                inclusive,
3272            } => Ok(Value::range_to_list(*start, *end, *inclusive)),
3273            _ => Err(IonError::type_err(
3274                format!("{}{}", ion_str!("cannot iterate over "), val.type_name()),
3275                span.line,
3276                span.col,
3277            )
3278            .into()),
3279        }
3280    }
3281
3282    // --- Pattern Matching ---
3283
3284    fn pattern_matches(&self, pattern: &Pattern, val: &Value) -> bool {
3285        match (pattern, val) {
3286            (Pattern::Wildcard, _) => true,
3287            (Pattern::Ident(_), _) => true,
3288            (Pattern::Int(a), Value::Int(b)) => a == b,
3289            (Pattern::Float(a), Value::Float(b)) => a == b,
3290            (Pattern::Bool(a), Value::Bool(b)) => a == b,
3291            (Pattern::Str(a), Value::Str(b)) => a == b,
3292            (Pattern::Bytes(a), Value::Bytes(b)) => a == b,
3293            (Pattern::None, Value::Option(None)) => true,
3294            (Pattern::Some(p), Value::Option(Some(v))) => self.pattern_matches(p, v),
3295            (Pattern::Ok(p), Value::Result(Ok(v))) => self.pattern_matches(p, v),
3296            (Pattern::Err(p), Value::Result(Err(v))) => self.pattern_matches(p, v),
3297            (Pattern::Tuple(pats), Value::Tuple(vals)) => {
3298                pats.len() == vals.len()
3299                    && pats
3300                        .iter()
3301                        .zip(vals)
3302                        .all(|(p, v)| self.pattern_matches(p, v))
3303            }
3304            (Pattern::List(pats, rest), Value::List(vals)) => {
3305                if rest.is_some() {
3306                    vals.len() >= pats.len()
3307                        && pats
3308                            .iter()
3309                            .zip(vals)
3310                            .all(|(p, v)| self.pattern_matches(p, v))
3311                } else {
3312                    pats.len() == vals.len()
3313                        && pats
3314                            .iter()
3315                            .zip(vals)
3316                            .all(|(p, v)| self.pattern_matches(p, v))
3317                }
3318            }
3319            (
3320                Pattern::EnumVariant {
3321                    enum_name,
3322                    variant,
3323                    fields,
3324                },
3325                Value::HostEnum {
3326                    enum_name: en,
3327                    variant: v,
3328                    data,
3329                },
3330            ) => {
3331                if enum_name != en || variant != v {
3332                    return false;
3333                }
3334                match fields {
3335                    EnumPatternFields::None => data.is_empty(),
3336                    EnumPatternFields::Positional(pats) => {
3337                        pats.len() == data.len()
3338                            && pats
3339                                .iter()
3340                                .zip(data)
3341                                .all(|(p, v)| self.pattern_matches(p, v))
3342                    }
3343                    EnumPatternFields::Named(_) => false, // named fields not applicable to enum data
3344                }
3345            }
3346            (
3347                Pattern::Struct { name, fields },
3348                Value::HostStruct {
3349                    type_name,
3350                    fields: val_fields,
3351                },
3352            ) => {
3353                if name != type_name {
3354                    return false;
3355                }
3356                fields.iter().all(|(fname, fpat)| {
3357                    match val_fields.get(fname) {
3358                        Some(v) => match fpat {
3359                            Some(p) => self.pattern_matches(p, v),
3360                            None => true, // just binding, always matches
3361                        },
3362                        None => false,
3363                    }
3364                })
3365            }
3366            _ => false,
3367        }
3368    }
3369
3370    fn bind_pattern(
3371        &mut self,
3372        pattern: &Pattern,
3373        val: &Value,
3374        mutable: bool,
3375        span: Span,
3376    ) -> Result<(), SignalOrError> {
3377        match (pattern, val) {
3378            (Pattern::Wildcard, _) => Ok(()),
3379            (Pattern::Ident(name), _) => {
3380                self.env.define(name.clone(), val.clone(), mutable);
3381                Ok(())
3382            }
3383            (
3384                Pattern::Int(_)
3385                | Pattern::Float(_)
3386                | Pattern::Bool(_)
3387                | Pattern::Str(_)
3388                | Pattern::Bytes(_)
3389                | Pattern::None,
3390                _,
3391            ) => Ok(()),
3392            (Pattern::Some(p), Value::Option(Some(v))) => self.bind_pattern(p, v, mutable, span),
3393            (Pattern::Ok(p), Value::Result(Ok(v))) => self.bind_pattern(p, v, mutable, span),
3394            (Pattern::Err(p), Value::Result(Err(v))) => self.bind_pattern(p, v, mutable, span),
3395            (Pattern::Tuple(pats), Value::Tuple(vals)) => {
3396                for (p, v) in pats.iter().zip(vals) {
3397                    self.bind_pattern(p, v, mutable, span)?;
3398                }
3399                Ok(())
3400            }
3401            (Pattern::List(pats, rest), Value::List(vals)) => {
3402                for (p, v) in pats.iter().zip(vals) {
3403                    self.bind_pattern(p, v, mutable, span)?;
3404                }
3405                if let Some(rest_pat) = rest {
3406                    let rest_vals = vals[pats.len()..].to_vec();
3407                    self.bind_pattern(rest_pat, &Value::List(rest_vals), mutable, span)?;
3408                }
3409                Ok(())
3410            }
3411            (Pattern::EnumVariant { fields, .. }, Value::HostEnum { data, .. }) => match fields {
3412                EnumPatternFields::None => Ok(()),
3413                EnumPatternFields::Positional(pats) => {
3414                    for (p, v) in pats.iter().zip(data) {
3415                        self.bind_pattern(p, v, mutable, span)?;
3416                    }
3417                    Ok(())
3418                }
3419                EnumPatternFields::Named(_) => Ok(()),
3420            },
3421            (
3422                Pattern::Struct { fields, .. },
3423                Value::HostStruct {
3424                    fields: val_fields, ..
3425                },
3426            ) => {
3427                for (fname, fpat) in fields {
3428                    if let Some(v) = val_fields.get(fname) {
3429                        match fpat {
3430                            Some(p) => self.bind_pattern(p, v, mutable, span)?,
3431                            None => self.env.define(fname.clone(), v.clone(), mutable),
3432                        }
3433                    }
3434                }
3435                Ok(())
3436            }
3437            _ => Err(IonError::runtime(
3438                ion_str!("pattern match failed in binding").to_string(),
3439                span.line,
3440                span.col,
3441            )
3442            .into()),
3443        }
3444    }
3445}
3446
3447#[cfg(feature = "concurrency")]
3448impl Interpreter {
3449    fn eval_async_block(&mut self, body: &[Stmt], _span: Span) -> SignalResult {
3450        use crate::async_rt::Nursery;
3451
3452        // Save and set nursery for this scope
3453        let prev_nursery = self.nursery.take();
3454        self.nursery = Some(Nursery::new());
3455
3456        self.env.push_scope();
3457        let result = self.eval_stmts(body);
3458        self.env.pop_scope();
3459
3460        // Join all spawned tasks (structured concurrency)
3461        let nursery = self.nursery.take().unwrap();
3462        self.nursery = prev_nursery;
3463
3464        if let Err(e) = nursery.join_all() {
3465            return Err(e.into());
3466        }
3467
3468        result
3469    }
3470
3471    fn eval_spawn(&mut self, expr: &Expr, span: Span) -> SignalResult {
3472        use std::sync::Arc;
3473
3474        // Require being inside an async block
3475        if self.nursery.is_none() {
3476            return Err(IonError::runtime(
3477                ion_str!("spawn is only allowed inside async {}").to_string(),
3478                span.line,
3479                span.col,
3480            )
3481            .into());
3482        }
3483
3484        // Capture current environment for the spawned task
3485        let captured_env = self.env.capture();
3486        let expr_clone = expr.clone();
3487        let limits = self.limits.clone();
3488        let types = self.types.clone();
3489
3490        let cancel_flag = Arc::new(std::sync::atomic::AtomicBool::new(false));
3491        let task_handle: Arc<dyn crate::async_rt::TaskHandle> =
3492            crate::async_rt::spawn_task_with_cancel(cancel_flag, move |flag| {
3493                let mut child = Interpreter::new();
3494                child.limits = limits;
3495                child.types = types;
3496                child.cancel_flag = Some(flag);
3497                // Load captured environment
3498                for (name, val) in captured_env {
3499                    child.env.define(name, val, false);
3500                }
3501                // Evaluate the expression
3502                let program = crate::ast::Program {
3503                    stmts: vec![crate::ast::Stmt {
3504                        kind: crate::ast::StmtKind::ExprStmt {
3505                            expr: expr_clone,
3506                            has_semi: false,
3507                        },
3508                        span: crate::ast::Span { line: 0, col: 0 },
3509                    }],
3510                };
3511                child.eval_program(&program)
3512            });
3513
3514        // Register with nursery
3515        if let Some(nursery) = &mut self.nursery {
3516            nursery.spawn(task_handle.clone());
3517        }
3518
3519        Ok(Value::Task(task_handle))
3520    }
3521
3522    fn eval_await(&mut self, expr: &Expr, span: Span) -> SignalResult {
3523        let val = self.eval_expr(expr)?;
3524        match val {
3525            Value::Task(handle) => handle.join().map_err(SignalOrError::Error),
3526            _ => Err(IonError::type_err(
3527                format!("{}{}", ion_str!("cannot await "), val.type_name()),
3528                span.line,
3529                span.col,
3530            )
3531            .into()),
3532        }
3533    }
3534
3535    fn eval_select(&mut self, branches: &[crate::ast::SelectBranch], span: Span) -> SignalResult {
3536        use std::sync::Arc;
3537
3538        // Spawn all branch futures as tasks
3539        let mut tasks: Vec<(usize, Arc<dyn crate::async_rt::TaskHandle>)> = Vec::new();
3540        for (i, branch) in branches.iter().enumerate() {
3541            let captured_env = self.env.capture();
3542            let expr_clone = branch.future_expr.clone();
3543            let limits = self.limits.clone();
3544            let types = self.types.clone();
3545
3546            let cancel_flag = Arc::new(std::sync::atomic::AtomicBool::new(false));
3547            let handle = crate::async_rt::spawn_task_with_cancel(cancel_flag, move |flag| {
3548                let mut child = Interpreter::new();
3549                child.limits = limits;
3550                child.types = types;
3551                child.cancel_flag = Some(flag);
3552                for (name, val) in captured_env {
3553                    child.env.define(name, val, false);
3554                }
3555                let program = crate::ast::Program {
3556                    stmts: vec![crate::ast::Stmt {
3557                        kind: crate::ast::StmtKind::ExprStmt {
3558                            expr: expr_clone,
3559                            has_semi: false,
3560                        },
3561                        span: crate::ast::Span { line: 0, col: 0 },
3562                    }],
3563                };
3564                child.eval_program(&program)
3565            });
3566            tasks.push((i, handle));
3567        }
3568
3569        // Wait for the first task to complete (condvar-based, no polling)
3570        let handles: Vec<_> = tasks.iter().map(|(_, h)| h.clone()).collect();
3571        let (winner_idx, result) = crate::async_rt::wait_any(&handles);
3572
3573        // Signal cancellation to losing branches so they terminate at
3574        // the next statement boundary instead of burning CPU.
3575        for (i, h) in handles.iter().enumerate() {
3576            if i != winner_idx {
3577                h.cancel();
3578            }
3579        }
3580
3581        let result = result?;
3582        let branch = &branches[tasks[winner_idx].0];
3583        self.env.push_scope();
3584        self.bind_pattern(&branch.pattern, &result, false, span)?;
3585        let body_result = self.eval_expr(&branch.body);
3586        self.env.pop_scope();
3587        body_result
3588    }
3589
3590    fn task_method(
3591        &self,
3592        handle: &std::sync::Arc<dyn crate::async_rt::TaskHandle>,
3593        method: &str,
3594        args: &[Value],
3595        span: Span,
3596    ) -> SignalResult {
3597        match method {
3598            "is_finished" => Ok(Value::Bool(handle.is_finished())),
3599            "cancel" => {
3600                handle.cancel();
3601                Ok(Value::Unit)
3602            }
3603            "is_cancelled" => Ok(Value::Bool(handle.is_cancelled())),
3604            "await_timeout" => {
3605                let ms = args.first().and_then(|v| v.as_int()).ok_or_else(|| {
3606                    IonError::runtime(
3607                        ion_str!("await_timeout requires int (ms)").to_string(),
3608                        span.line,
3609                        span.col,
3610                    )
3611                })?;
3612                match handle.join_timeout(std::time::Duration::from_millis(ms as u64)) {
3613                    Some(result) => {
3614                        let val = result.map_err(SignalOrError::Error)?;
3615                        Ok(Value::Option(Some(Box::new(val))))
3616                    }
3617                    None => Ok(Value::Option(None)),
3618                }
3619            }
3620            _ => Err(IonError::type_err(
3621                format!(
3622                    "{}{}{}",
3623                    ion_str!("no method '"),
3624                    method,
3625                    ion_str!("' on Task")
3626                ),
3627                span.line,
3628                span.col,
3629            )
3630            .into()),
3631        }
3632    }
3633
3634    fn channel_method(
3635        &self,
3636        ch: &crate::async_rt::ChannelEnd,
3637        method: &str,
3638        args: &[Value],
3639        span: Span,
3640    ) -> SignalResult {
3641        use crate::async_rt::ChannelEnd;
3642        match (ch, method) {
3643            (ChannelEnd::Sender(tx), "send") => {
3644                if args.is_empty() {
3645                    return Err(IonError::runtime(
3646                        ion_str!("send requires a value").to_string(),
3647                        span.line,
3648                        span.col,
3649                    )
3650                    .into());
3651                }
3652                tx.send(args[0].clone()).map_err(|e| {
3653                    IonError::runtime(
3654                        format!("{}{}", ion_str!("channel send failed: "), e.message),
3655                        span.line,
3656                        span.col,
3657                    )
3658                })?;
3659                Ok(Value::Unit)
3660            }
3661            (ChannelEnd::Sender(tx), "close") => {
3662                tx.close();
3663                Ok(Value::Unit)
3664            }
3665            (ChannelEnd::Receiver(rx), "recv") => match rx.recv() {
3666                Some(v) => Ok(Value::Option(Some(Box::new(v)))),
3667                None => Ok(Value::Option(None)),
3668            },
3669            (ChannelEnd::Receiver(rx), "try_recv") => match rx.try_recv() {
3670                Some(v) => Ok(Value::Option(Some(Box::new(v)))),
3671                None => Ok(Value::Option(None)),
3672            },
3673            (ChannelEnd::Receiver(rx), "recv_timeout") => {
3674                if args.is_empty() {
3675                    return Err(IonError::runtime(
3676                        ion_str!("recv_timeout requires a timeout in ms").to_string(),
3677                        span.line,
3678                        span.col,
3679                    )
3680                    .into());
3681                }
3682                let ms = args[0].as_int().ok_or_else(|| {
3683                    IonError::runtime(
3684                        ion_str!("recv_timeout requires int (ms)").to_string(),
3685                        span.line,
3686                        span.col,
3687                    )
3688                })?;
3689                match rx.recv_timeout(std::time::Duration::from_millis(ms as u64)) {
3690                    Some(v) => Ok(Value::Option(Some(Box::new(v)))),
3691                    None => Ok(Value::Option(None)),
3692                }
3693            }
3694            _ => Err(IonError::type_err(
3695                format!(
3696                    "{}{}{}",
3697                    ion_str!("no method '"),
3698                    method,
3699                    ion_str!("' on Channel")
3700                ),
3701                span.line,
3702                span.col,
3703            )
3704            .into()),
3705        }
3706    }
3707}
3708
3709pub fn register_builtins(env: &mut Env, output: Arc<dyn OutputHandler>) {
3710    // I/O: moved to io:: module
3711    // Math: moved to math:: module
3712    // JSON/Msgpack: moved to json:: module
3713
3714    env.define(
3715        ion_str!("len").to_string(),
3716        Value::BuiltinFn(ion_str!("len").to_string(), |args| {
3717            if args.is_empty() {
3718                return Err(ion_str!("len() requires 1 argument"));
3719            }
3720            match &args[0] {
3721                Value::List(items) => Ok(Value::Int(items.len() as i64)),
3722                Value::Str(s) => Ok(Value::Int(s.len() as i64)),
3723                Value::Dict(map) => Ok(Value::Int(map.len() as i64)),
3724                Value::Bytes(b) => Ok(Value::Int(b.len() as i64)),
3725                _ => Err(format!(
3726                    "{}{}",
3727                    ion_str!("len() not supported for "),
3728                    args[0].type_name()
3729                )),
3730            }
3731        }),
3732        false,
3733    );
3734    env.define(
3735        ion_str!("range").to_string(),
3736        Value::BuiltinFn(ion_str!("range").to_string(), |args| match args.len() {
3737            1 => {
3738                let n = args[0].as_int().ok_or(ion_str!("range requires int"))?;
3739                Ok(Value::Range {
3740                    start: 0,
3741                    end: n,
3742                    inclusive: false,
3743                })
3744            }
3745            2 => {
3746                let start = args[0].as_int().ok_or(ion_str!("range requires int"))?;
3747                let end = args[1].as_int().ok_or(ion_str!("range requires int"))?;
3748                Ok(Value::Range {
3749                    start,
3750                    end,
3751                    inclusive: false,
3752                })
3753            }
3754            _ => Err(ion_str!("range takes 1 or 2 arguments").to_string()),
3755        }),
3756        false,
3757    );
3758    env.define(
3759        ion_str!("set"),
3760        Value::BuiltinFn(ion_str!("set"), |args| {
3761            if args.is_empty() {
3762                return Ok(Value::Set(vec![]));
3763            }
3764            match &args[0] {
3765                Value::List(items) => {
3766                    let mut unique = Vec::new();
3767                    for v in items {
3768                        if !unique.iter().any(|x| x == v) {
3769                            unique.push(v.clone());
3770                        }
3771                    }
3772                    Ok(Value::Set(unique))
3773                }
3774                _ => Err(ion_str!("set() requires a list argument")),
3775            }
3776        }),
3777        false,
3778    );
3779    env.define(
3780        ion_str!("cell"),
3781        Value::BuiltinFn(ion_str!("cell"), |args| {
3782            if args.len() != 1 {
3783                return Err(ion_str!("cell() takes 1 argument"));
3784            }
3785            Ok(Value::Cell(std::sync::Arc::new(std::sync::Mutex::new(
3786                args[0].clone(),
3787            ))))
3788        }),
3789        false,
3790    );
3791    env.define(
3792        ion_str!("type_of").to_string(),
3793        Value::BuiltinFn(ion_str!("type_of").to_string(), |args| {
3794            if args.is_empty() {
3795                return Err(ion_str!("type_of() requires 1 argument"));
3796            }
3797            Ok(Value::Str(args[0].type_name().to_string()))
3798        }),
3799        false,
3800    );
3801    // json_encode, json_decode, abs, min, max → moved to json:: / math:: modules
3802    env.define(
3803        ion_str!("str").to_string(),
3804        Value::BuiltinFn(ion_str!("str").to_string(), |args| {
3805            if args.len() != 1 {
3806                return Err(ion_str!("str takes 1 argument"));
3807            }
3808            Ok(Value::Str(args[0].to_string()))
3809        }),
3810        false,
3811    );
3812    env.define(
3813        ion_str!("int").to_string(),
3814        Value::BuiltinFn(ion_str!("int").to_string(), |args| {
3815            if args.len() != 1 {
3816                return Err(ion_str!("int takes 1 argument"));
3817            }
3818            match &args[0] {
3819                Value::Int(n) => Ok(Value::Int(*n)),
3820                Value::Float(n) => Ok(Value::Int(*n as i64)),
3821                Value::Str(s) => s.parse::<i64>().map(Value::Int).map_err(|_| {
3822                    format!(
3823                        "{}{}{}",
3824                        ion_str!("cannot convert '"),
3825                        s,
3826                        ion_str!("' to int")
3827                    )
3828                }),
3829                Value::Bool(b) => Ok(Value::Int(if *b { 1 } else { 0 })),
3830                _ => Err(format!(
3831                    "{}{}",
3832                    ion_str!("cannot convert "),
3833                    args[0].type_name()
3834                )),
3835            }
3836        }),
3837        false,
3838    );
3839    env.define(
3840        ion_str!("float").to_string(),
3841        Value::BuiltinFn(ion_str!("float").to_string(), |args| {
3842            if args.len() != 1 {
3843                return Err(ion_str!("float takes 1 argument"));
3844            }
3845            match &args[0] {
3846                Value::Float(n) => Ok(Value::Float(*n)),
3847                Value::Int(n) => Ok(Value::Float(*n as f64)),
3848                Value::Str(s) => s.parse::<f64>().map(Value::Float).map_err(|_| {
3849                    format!(
3850                        "{}{}{}",
3851                        ion_str!("cannot convert '"),
3852                        s,
3853                        ion_str!("' to float")
3854                    )
3855                }),
3856                _ => Err(format!(
3857                    "{}{}",
3858                    ion_str!("cannot convert "),
3859                    args[0].type_name()
3860                )),
3861            }
3862        }),
3863        false,
3864    );
3865    // floor, ceil, round, pow, sqrt, clamp → moved to math:: module
3866    // join → moved to str:: module
3867    // json_encode_pretty, msgpack_encode, msgpack_decode → moved to json:: module
3868    env.define(
3869        ion_str!("enumerate").to_string(),
3870        Value::BuiltinFn(ion_str!("enumerate").to_string(), |args| {
3871            if args.len() != 1 {
3872                return Err(ion_str!("enumerate takes 1 argument"));
3873            }
3874            match &args[0] {
3875                Value::List(items) => Ok(Value::List(
3876                    items
3877                        .iter()
3878                        .enumerate()
3879                        .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v.clone()]))
3880                        .collect(),
3881                )),
3882                Value::Str(s) => Ok(Value::List(
3883                    s.chars()
3884                        .enumerate()
3885                        .map(|(i, c)| {
3886                            Value::Tuple(vec![Value::Int(i as i64), Value::Str(c.to_string())])
3887                        })
3888                        .collect(),
3889                )),
3890                Value::Dict(map) => Ok(Value::List(
3891                    map.iter()
3892                        .enumerate()
3893                        .map(|(i, (k, v))| {
3894                            Value::Tuple(vec![
3895                                Value::Int(i as i64),
3896                                Value::Tuple(vec![Value::Str(k.clone()), v.clone()]),
3897                            ])
3898                        })
3899                        .collect(),
3900                )),
3901                _ => Err(format!(
3902                    "{}{}",
3903                    ion_str!("enumerate() not supported for "),
3904                    args[0].type_name()
3905                )),
3906            }
3907        }),
3908        false,
3909    );
3910
3911    env.define(
3912        ion_str!("bytes").to_string(),
3913        Value::BuiltinFn(ion_str!("bytes").to_string(), |args| match args.first() {
3914            Some(Value::List(items)) => {
3915                let mut bytes = Vec::with_capacity(items.len());
3916                for item in items {
3917                    let n = item
3918                        .as_int()
3919                        .ok_or_else(|| ion_str!("bytes() list items must be ints"))?;
3920                    if !(0..=255).contains(&n) {
3921                        return Err(format!("{}{}", ion_str!("byte value out of range: "), n));
3922                    }
3923                    bytes.push(n as u8);
3924                }
3925                Ok(Value::Bytes(bytes))
3926            }
3927            Some(Value::Str(s)) => Ok(Value::Bytes(s.as_bytes().to_vec())),
3928            Some(Value::Int(n)) if *n >= 0 && *n <= 10_000_000 => {
3929                Ok(Value::Bytes(vec![0u8; *n as usize]))
3930            }
3931            Some(Value::Int(n)) => Err(format!(
3932                "bytes(n): size {} is out of range (0..10_000_000)",
3933                n
3934            )),
3935            None => Ok(Value::Bytes(Vec::new())),
3936            _ => Err(format!(
3937                "{}{}",
3938                ion_str!("bytes() not supported for "),
3939                args[0].type_name()
3940            )),
3941        }),
3942        false,
3943    );
3944    env.define(
3945        ion_str!("bytes_from_hex").to_string(),
3946        Value::BuiltinFn(ion_str!("bytes_from_hex").to_string(), |args| {
3947            if args.len() != 1 {
3948                return Err(ion_str!("bytes_from_hex takes 1 argument"));
3949            }
3950            let s = args[0]
3951                .as_str()
3952                .ok_or_else(|| ion_str!("bytes_from_hex requires a string"))?;
3953            if !s.is_ascii() {
3954                return Err(ion_str!("bytes_from_hex requires an ASCII hex string"));
3955            }
3956            if s.len() % 2 != 0 {
3957                return Err(ion_str!("hex string must have even length").to_string());
3958            }
3959            let mut bytes = Vec::with_capacity(s.len() / 2);
3960            for i in (0..s.len()).step_by(2) {
3961                let byte = u8::from_str_radix(&s[i..i + 2], 16)
3962                    .map_err(|_| format!("{}{}", ion_str!("invalid hex: "), &s[i..i + 2]))?;
3963                bytes.push(byte);
3964            }
3965            Ok(Value::Bytes(bytes))
3966        }),
3967        false,
3968    );
3969
3970    env.define(
3971        ion_str!("assert").to_string(),
3972        Value::BuiltinFn(ion_str!("assert").to_string(), |args| {
3973            if args.is_empty() {
3974                return Err(ion_str!("assert requires at least 1 argument").to_string());
3975            }
3976            let condition = match &args[0] {
3977                Value::Bool(b) => *b,
3978                _ => {
3979                    return Err(format!(
3980                        "{}{}",
3981                        ion_str!("assert condition must be bool, got "),
3982                        args[0].type_name()
3983                    ))
3984                }
3985            };
3986            if !condition {
3987                let msg = if args.len() > 1 {
3988                    args[1].to_string()
3989                } else {
3990                    ion_str!("assertion failed").to_string()
3991                };
3992                return Err(msg);
3993            }
3994            Ok(Value::Unit)
3995        }),
3996        false,
3997    );
3998
3999    env.define(
4000        ion_str!("assert_eq").to_string(),
4001        Value::BuiltinFn(ion_str!("assert_eq").to_string(), |args| {
4002            if args.len() < 2 {
4003                return Err(ion_str!("assert_eq requires at least 2 arguments").to_string());
4004            }
4005            if args[0] != args[1] {
4006                let msg = if args.len() > 2 {
4007                    format!(
4008                        "{}{}{}{}{}",
4009                        args[2],
4010                        ion_str!(": expected "),
4011                        args[0],
4012                        ion_str!(", got "),
4013                        args[1]
4014                    )
4015                } else {
4016                    format!(
4017                        "{}{}{}{}",
4018                        ion_str!("assertion failed: expected "),
4019                        args[0],
4020                        ion_str!(", got "),
4021                        args[1]
4022                    )
4023                };
4024                return Err(msg);
4025            }
4026            Ok(Value::Unit)
4027        }),
4028        false,
4029    );
4030
4031    #[cfg(feature = "concurrency")]
4032    {
4033        env.define(
4034            ion_str!("sleep").to_string(),
4035            Value::BuiltinFn(ion_str!("sleep").to_string(), |args| {
4036                let ms = args
4037                    .first()
4038                    .and_then(|v| v.as_int())
4039                    .ok_or(ion_str!("sleep requires int (ms)"))?;
4040                crate::async_rt::sleep(std::time::Duration::from_millis(ms as u64));
4041                Ok(Value::Unit)
4042            }),
4043            false,
4044        );
4045        env.define(
4046            ion_str!("timeout").to_string(),
4047            Value::BuiltinFn(ion_str!("timeout").to_string(), |_args| {
4048                // Actual implementation is in call_value (needs interpreter context)
4049                Err(ion_str!("timeout: internal error (should not reach here)"))
4050            }),
4051            false,
4052        );
4053        env.define(
4054            ion_str!("channel").to_string(),
4055            Value::BuiltinFn(ion_str!("channel").to_string(), |args| {
4056                let buffer = if args.is_empty() {
4057                    16
4058                } else {
4059                    args[0]
4060                        .as_int()
4061                        .ok_or(ion_str!("channel buffer size must be int"))?
4062                        as usize
4063                };
4064                let (tx, rx) = crate::async_rt::create_channel(buffer);
4065                Ok(Value::Tuple(vec![tx, rx]))
4066            }),
4067            false,
4068        );
4069    }
4070
4071    // Register stdlib modules (math, json, io)
4072    crate::stdlib::register_stdlib_with_output(env, output);
4073}