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