Skip to main content

ion_core/
vm.rs

1//! Stack-based virtual machine for executing Ion bytecode.
2
3use indexmap::IndexMap;
4
5use crate::bytecode::{Chunk, Op};
6use crate::env::Env;
7use crate::error::IonError;
8use crate::host_types::TypeRegistry;
9use crate::stdlib::{OutputHandler, OutputStream};
10use crate::value::Value;
11use std::sync::Arc;
12
13/// A local variable stored in a stack slot.
14#[derive(Debug, Clone)]
15struct LocalSlot {
16    value: Value,
17    mutable: bool,
18}
19
20/// An exception handler for try/catch blocks.
21#[derive(Debug, Clone)]
22struct ExceptionHandler {
23    /// IP to jump to (start of catch block).
24    catch_ip: usize,
25    /// Stack depth to restore on catch.
26    stack_depth: usize,
27    /// Local frames depth to restore on catch.
28    local_frames_depth: usize,
29    /// Locals depth to restore on catch.
30    locals_depth: usize,
31}
32
33/// The Ion virtual machine.
34pub struct Vm {
35    /// Value stack.
36    stack: Vec<Value>,
37    /// Environment for variable bindings (globals and fallback).
38    env: Env,
39    /// Instruction pointer.
40    ip: usize,
41    /// Iterator stack for for-loops.
42    iterators: Vec<Box<dyn Iterator<Item = Value>>>,
43    /// Compilation cache: fn_id -> compiled bytecode chunk.
44    fn_cache: std::collections::HashMap<u64, crate::bytecode::Chunk>,
45    /// Pending tail call: (func, args) to be executed by the trampoline.
46    pending_tail_call: Option<(Value, Vec<Value>)>,
47    /// Stack-slot local variables (fast indexed access).
48    locals: Vec<LocalSlot>,
49    /// Scope boundaries in the locals array (each entry is the locals.len() at scope start).
50    local_frames: Vec<usize>,
51    /// Base offset for the current function's locals (slot indices are relative to this).
52    locals_base: usize,
53    /// Host type registry for struct/enum construction.
54    types: TypeRegistry,
55    /// Exception handler stack for try/catch.
56    exception_handlers: Vec<ExceptionHandler>,
57    /// Host output handler for bytecode print operations.
58    output: Arc<dyn OutputHandler>,
59}
60
61impl Default for Vm {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl Vm {
68    pub fn new() -> Self {
69        Self {
70            stack: Vec::with_capacity(256),
71            env: Env::new(),
72            ip: 0,
73            iterators: Vec::new(),
74            fn_cache: std::collections::HashMap::new(),
75            pending_tail_call: None,
76            locals: Vec::with_capacity(64),
77            local_frames: Vec::with_capacity(16),
78            locals_base: 0,
79            types: TypeRegistry::default(),
80            exception_handlers: Vec::new(),
81            output: crate::stdlib::missing_output_handler(),
82        }
83    }
84
85    /// Create a VM with an existing environment (for engine integration).
86    pub fn with_env(env: Env) -> Self {
87        Self::with_env_and_output(env, crate::stdlib::missing_output_handler())
88    }
89
90    /// Create a VM with an existing environment and output handler.
91    pub fn with_env_and_output(env: Env, output: Arc<dyn OutputHandler>) -> Self {
92        Self {
93            stack: Vec::with_capacity(256),
94            env,
95            ip: 0,
96            iterators: Vec::new(),
97            fn_cache: std::collections::HashMap::new(),
98            pending_tail_call: None,
99            locals: Vec::with_capacity(64),
100            local_frames: Vec::with_capacity(16),
101            locals_base: 0,
102            types: TypeRegistry::default(),
103            exception_handlers: Vec::new(),
104            output,
105        }
106    }
107
108    /// Set the host output handler for bytecode print operations.
109    pub fn set_output_handler(&mut self, output: Arc<dyn OutputHandler>) {
110        self.output = output;
111    }
112
113    /// Set the type registry for host type construction.
114    pub fn set_types(&mut self, types: TypeRegistry) {
115        self.types = types;
116    }
117
118    /// Get a reference to the environment.
119    pub fn env(&self) -> &Env {
120        &self.env
121    }
122
123    /// Get a mutable reference to the environment.
124    pub fn env_mut(&mut self) -> &mut Env {
125        &mut self.env
126    }
127
128    /// Pre-populate the function cache with precompiled chunks from the compiler.
129    pub fn preload_fn_chunks(&mut self, chunks: crate::value::FnChunkCache) {
130        self.fn_cache.extend(chunks);
131    }
132
133    /// Execute a compiled chunk, returning the final value.
134    pub fn execute(&mut self, chunk: &Chunk) -> Result<Value, IonError> {
135        self.ip = 0;
136        self.stack.clear();
137        match self.run_chunk(chunk) {
138            Ok(v) => Ok(v),
139            Err(e) if e.kind == crate::error::ErrorKind::PropagatedErr => {
140                Ok(Value::Result(Err(Box::new(Value::Str(e.message.clone())))))
141            }
142            Err(e) if e.kind == crate::error::ErrorKind::PropagatedNone => Ok(Value::Option(None)),
143            Err(e) => Err(e),
144        }
145    }
146
147    /// Run a chunk without resetting state (used for recursive function calls).
148    fn run_chunk(&mut self, chunk: &Chunk) -> Result<Value, IonError> {
149        while self.ip < chunk.code.len() {
150            let op_byte = chunk.code[self.ip];
151            let line = chunk.lines[self.ip];
152            let col = chunk.cols[self.ip];
153            self.ip += 1;
154
155            let op = match self.decode_op(op_byte, line, col) {
156                Ok(op) => op,
157                Err(e) => {
158                    if let Some(handler) = self.exception_handlers.pop() {
159                        self.stack.truncate(handler.stack_depth);
160                        self.locals.truncate(handler.locals_depth);
161                        self.local_frames.truncate(handler.local_frames_depth);
162                        self.stack.push(Value::Str(e.message.clone()));
163                        self.ip = handler.catch_ip;
164                        continue;
165                    }
166                    return Err(e);
167                }
168            };
169
170            // Handle TryBegin/TryEnd before dispatch_instruction
171            match op {
172                Op::TryBegin => {
173                    let offset = chunk.read_u16(self.ip) as usize;
174                    self.ip += 2;
175                    let catch_ip = self.ip + offset;
176                    self.exception_handlers.push(ExceptionHandler {
177                        catch_ip,
178                        stack_depth: self.stack.len(),
179                        local_frames_depth: self.local_frames.len(),
180                        locals_depth: self.locals.len(),
181                    });
182                    continue;
183                }
184                Op::TryEnd => {
185                    let offset = chunk.read_u16(self.ip) as usize;
186                    self.ip += 2;
187                    self.exception_handlers.pop();
188                    self.ip += offset; // jump over catch block
189                    continue;
190                }
191                _ => {}
192            }
193
194            match self.dispatch_instruction(op, chunk, line, col) {
195                Ok(Some(val)) => return Ok(val),
196                Ok(None) => {}
197                Err(e) => {
198                    // Check if we're inside a try block
199                    if e.kind != crate::error::ErrorKind::PropagatedErr
200                        && e.kind != crate::error::ErrorKind::PropagatedNone
201                    {
202                        if let Some(handler) = self.exception_handlers.pop() {
203                            self.stack.truncate(handler.stack_depth);
204                            self.locals.truncate(handler.locals_depth);
205                            self.local_frames.truncate(handler.local_frames_depth);
206                            self.stack.push(Value::Str(e.message.clone()));
207                            self.ip = handler.catch_ip;
208                            continue;
209                        }
210                    }
211                    return Err(e);
212                }
213            }
214        }
215        Ok(self.stack.pop().unwrap_or(Value::Unit))
216    }
217
218    /// Dispatch a single instruction. Returns Ok(Some(val)) for Return, Ok(None) to continue.
219    fn dispatch_instruction(
220        &mut self,
221        op: Op,
222        chunk: &Chunk,
223        line: usize,
224        col: usize,
225    ) -> Result<Option<Value>, IonError> {
226        match op {
227            Op::Constant => {
228                let idx = chunk.read_u16(self.ip) as usize;
229                self.ip += 2;
230                let val = chunk.constants[idx].clone();
231                self.stack.push(val);
232            }
233
234            Op::True => self.stack.push(Value::Bool(true)),
235            Op::False => self.stack.push(Value::Bool(false)),
236            Op::Unit => self.stack.push(Value::Unit),
237            Op::None => self.stack.push(Value::Option(None)),
238
239            // --- Arithmetic ---
240            Op::Add => {
241                let b = self.pop(line, col)?;
242                let a = self.pop(line, col)?;
243                self.stack.push(self.op_add(a, b, line, col)?);
244            }
245            Op::Sub => {
246                let b = self.pop(line, col)?;
247                let a = self.pop(line, col)?;
248                self.stack.push(self.op_sub(a, b, line, col)?);
249            }
250            Op::Mul => {
251                let b = self.pop(line, col)?;
252                let a = self.pop(line, col)?;
253                self.stack.push(self.op_mul(a, b, line, col)?);
254            }
255            Op::Div => {
256                let b = self.pop(line, col)?;
257                let a = self.pop(line, col)?;
258                self.stack.push(self.op_div(a, b, line, col)?);
259            }
260            Op::Mod => {
261                let b = self.pop(line, col)?;
262                let a = self.pop(line, col)?;
263                self.stack.push(self.op_mod(a, b, line, col)?);
264            }
265            Op::Neg => {
266                let val = self.pop(line, col)?;
267                self.stack.push(self.op_neg(val, line, col)?);
268            }
269
270            // --- Bitwise ---
271            Op::BitAnd => {
272                let b = self.pop(line, col)?;
273                let a = self.pop(line, col)?;
274                match (a, b) {
275                    (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x & y)),
276                    (a, b) => {
277                        return Err(IonError::type_err(
278                            format!(
279                                "{}{} and {}",
280                                ion_str!("'&' expects int, got "),
281                                a.type_name(),
282                                b.type_name()
283                            ),
284                            line,
285                            col,
286                        ))
287                    }
288                }
289            }
290            Op::BitOr => {
291                let b = self.pop(line, col)?;
292                let a = self.pop(line, col)?;
293                match (a, b) {
294                    (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x | y)),
295                    (a, b) => {
296                        return Err(IonError::type_err(
297                            format!(
298                                "{}{} and {}",
299                                ion_str!("'|' expects int, got "),
300                                a.type_name(),
301                                b.type_name()
302                            ),
303                            line,
304                            col,
305                        ))
306                    }
307                }
308            }
309            Op::BitXor => {
310                let b = self.pop(line, col)?;
311                let a = self.pop(line, col)?;
312                match (a, b) {
313                    (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x ^ y)),
314                    (a, b) => {
315                        return Err(IonError::type_err(
316                            format!(
317                                "{}{} and {}",
318                                ion_str!("'^' expects int, got "),
319                                a.type_name(),
320                                b.type_name()
321                            ),
322                            line,
323                            col,
324                        ))
325                    }
326                }
327            }
328            Op::Shl => {
329                let b = self.pop(line, col)?;
330                let a = self.pop(line, col)?;
331                match (a, b) {
332                    (Value::Int(x), Value::Int(y)) if (0..64).contains(&y) => {
333                        self.stack.push(Value::Int(x << y))
334                    }
335                    (Value::Int(_), Value::Int(y)) => {
336                        return Err(IonError::runtime(
337                            format!("shift count {} is out of range 0..64", y),
338                            line,
339                            col,
340                        ))
341                    }
342                    (a, b) => {
343                        return Err(IonError::type_err(
344                            format!(
345                                "{}{} and {}",
346                                ion_str!("'<<' expects int, got "),
347                                a.type_name(),
348                                b.type_name()
349                            ),
350                            line,
351                            col,
352                        ))
353                    }
354                }
355            }
356            Op::Shr => {
357                let b = self.pop(line, col)?;
358                let a = self.pop(line, col)?;
359                match (a, b) {
360                    (Value::Int(x), Value::Int(y)) if (0..64).contains(&y) => {
361                        self.stack.push(Value::Int(x >> y))
362                    }
363                    (Value::Int(_), Value::Int(y)) => {
364                        return Err(IonError::runtime(
365                            format!("shift count {} is out of range 0..64", y),
366                            line,
367                            col,
368                        ))
369                    }
370                    (a, b) => {
371                        return Err(IonError::type_err(
372                            format!(
373                                "{}{} and {}",
374                                ion_str!("'>>' expects int, got "),
375                                a.type_name(),
376                                b.type_name()
377                            ),
378                            line,
379                            col,
380                        ))
381                    }
382                }
383            }
384
385            // --- Comparison ---
386            Op::Eq => {
387                let b = self.pop(line, col)?;
388                let a = self.pop(line, col)?;
389                self.stack.push(Value::Bool(a == b));
390            }
391            Op::NotEq => {
392                let b = self.pop(line, col)?;
393                let a = self.pop(line, col)?;
394                self.stack.push(Value::Bool(a != b));
395            }
396            Op::Lt => {
397                let b = self.pop(line, col)?;
398                let a = self.pop(line, col)?;
399                self.stack
400                    .push(Value::Bool(self.compare_lt(&a, &b, line, col)?));
401            }
402            Op::Gt => {
403                let b = self.pop(line, col)?;
404                let a = self.pop(line, col)?;
405                self.stack
406                    .push(Value::Bool(self.compare_lt(&b, &a, line, col)?));
407            }
408            Op::LtEq => {
409                let b = self.pop(line, col)?;
410                let a = self.pop(line, col)?;
411                self.stack
412                    .push(Value::Bool(!self.compare_lt(&b, &a, line, col)?));
413            }
414            Op::GtEq => {
415                let b = self.pop(line, col)?;
416                let a = self.pop(line, col)?;
417                self.stack
418                    .push(Value::Bool(!self.compare_lt(&a, &b, line, col)?));
419            }
420
421            // --- Logic ---
422            Op::Not => {
423                let val = self.pop(line, col)?;
424                self.stack.push(Value::Bool(!val.is_truthy()));
425            }
426            Op::And => {
427                let offset = chunk.read_u16(self.ip) as usize;
428                self.ip += 2;
429                if !self.is_top_truthy(line, col)? {
430                    self.ip += offset; // short-circuit: keep falsy value
431                }
432                // If truthy, fall through — Pop will remove it, then eval right
433            }
434            Op::Or => {
435                let offset = chunk.read_u16(self.ip) as usize;
436                self.ip += 2;
437                if self.is_top_truthy(line, col)? {
438                    self.ip += offset; // short-circuit: keep truthy value
439                }
440            }
441
442            // --- Variables ---
443            Op::DefineLocal => {
444                let name_idx = chunk.read_u16(self.ip) as usize;
445                self.ip += 2;
446                let mutable = chunk.read_u8(self.ip) != 0;
447                self.ip += 1;
448                let sym = self.const_to_sym(&chunk.constants[name_idx], line, col)?;
449                let val = self.pop(line, col)?;
450                self.env.define_sym(sym, val, mutable);
451            }
452            Op::GetLocal => {
453                let name_idx = chunk.read_u16(self.ip) as usize;
454                self.ip += 2;
455                let sym = self.const_to_sym(&chunk.constants[name_idx], line, col)?;
456                let val = self.env.get_sym(sym).cloned().ok_or_else(|| {
457                    let name = self.env.resolve(sym);
458                    IonError::name(
459                        format!("{}{}", ion_str!("undefined variable: "), name),
460                        line,
461                        col,
462                    )
463                })?;
464                self.stack.push(val);
465            }
466            Op::SetLocal => {
467                let name_idx = chunk.read_u16(self.ip) as usize;
468                self.ip += 2;
469                let sym = self.const_to_sym(&chunk.constants[name_idx], line, col)?;
470                let val = self.pop(line, col)?;
471                self.env
472                    .set_sym(sym, val.clone())
473                    .map_err(|e| IonError::runtime(e, line, col))?;
474                self.stack.push(val); // assignment is an expression
475            }
476            Op::GetGlobal => {
477                let name_idx = chunk.read_u16(self.ip) as usize;
478                self.ip += 2;
479                let sym = self.const_to_sym(&chunk.constants[name_idx], line, col)?;
480                let val = self.env.get_sym(sym).cloned().ok_or_else(|| {
481                    let name = self.env.resolve(sym);
482                    IonError::name(
483                        format!("{}{}", ion_str!("undefined variable: "), name),
484                        line,
485                        col,
486                    )
487                })?;
488                self.stack.push(val);
489            }
490            Op::SetGlobal => {
491                let name_idx = chunk.read_u16(self.ip) as usize;
492                self.ip += 2;
493                let sym = self.const_to_sym(&chunk.constants[name_idx], line, col)?;
494                let val = self.pop(line, col)?;
495                self.env
496                    .set_sym(sym, val.clone())
497                    .map_err(|e| IonError::runtime(e, line, col))?;
498                self.stack.push(val);
499            }
500
501            // --- Stack-slot locals (fast path) ---
502            Op::DefineLocalSlot => {
503                let mutable = chunk.read_u8(self.ip) != 0;
504                self.ip += 1;
505                let val = self.pop(line, col)?;
506                self.locals.push(LocalSlot {
507                    value: val,
508                    mutable,
509                });
510            }
511            Op::ImportGlob => {
512                let module = self.pop(line, col)?;
513                let Value::Dict(map) = module else {
514                    return Err(IonError::type_err(
515                        ion_str!("use target is not a module"),
516                        line,
517                        col,
518                    ));
519                };
520                for (name, value) in map {
521                    let sym = self.env.intern(&name);
522                    self.env.define_sym(sym, value, false);
523                }
524            }
525            Op::GetLocalSlot => {
526                let slot = self.locals_base + chunk.read_u16(self.ip) as usize;
527                self.ip += 2;
528                let val = self.locals[slot].value.clone();
529                self.stack.push(val);
530            }
531            Op::SetLocalSlot => {
532                let slot = self.locals_base + chunk.read_u16(self.ip) as usize;
533                self.ip += 2;
534                let val = self.pop(line, col)?;
535                if !self.locals[slot].mutable {
536                    return Err(IonError::runtime(
537                        ion_str!("cannot assign to immutable variable"),
538                        line,
539                        col,
540                    ));
541                }
542                self.locals[slot].value = val.clone();
543                self.stack.push(val); // assignment is an expression
544            }
545
546            // --- Control flow ---
547            Op::Jump => {
548                let offset = chunk.read_u16(self.ip) as usize;
549                self.ip += 2;
550                self.ip += offset;
551            }
552            Op::JumpIfFalse => {
553                let offset = chunk.read_u16(self.ip) as usize;
554                self.ip += 2;
555                if !self.is_top_truthy(line, col)? {
556                    self.ip += offset;
557                }
558            }
559            Op::Loop => {
560                let offset = chunk.read_u16(self.ip) as usize;
561                self.ip += 2;
562                self.ip -= offset;
563            }
564
565            // --- Functions ---
566            Op::Call => {
567                let arg_count = chunk.read_u8(self.ip) as usize;
568                self.ip += 1;
569                self.call_function(arg_count, line, col)?;
570            }
571            Op::CallNamed => {
572                let arg_count = chunk.read_u8(self.ip) as usize;
573                self.ip += 1;
574                let named_count = chunk.read_u8(self.ip) as usize;
575                self.ip += 1;
576                // Read named arg metadata: (position, name)
577                let mut named_map: Vec<(usize, String)> = Vec::with_capacity(named_count);
578                for _ in 0..named_count {
579                    let pos = chunk.read_u8(self.ip) as usize;
580                    self.ip += 1;
581                    let name_idx = chunk.read_u16(self.ip) as usize;
582                    self.ip += 2;
583                    if let Value::Str(name) = &chunk.constants[name_idx] {
584                        named_map.push((pos, name.clone()));
585                    }
586                }
587                self.call_function_named(arg_count, &named_map, line, col)?;
588            }
589            Op::TailCall => {
590                let arg_count = chunk.read_u8(self.ip) as usize;
591                self.ip += 1;
592                // Extract func and args for trampoline
593                let args_start = self.stack.len() - arg_count;
594                let func_idx = args_start - 1;
595                let func = self.stack[func_idx].clone();
596                let args: Vec<Value> = self.stack[args_start..].to_vec();
597                self.stack.truncate(func_idx);
598                self.pending_tail_call = Some((func, args));
599                return Ok(Some(Value::Unit)); // value is unused; caller checks pending_tail_call
600            }
601            Op::Return => {
602                // Return the top of stack value
603                let val = if self.stack.is_empty() {
604                    Value::Unit
605                } else {
606                    self.pop(line, col)?
607                };
608                return Ok(Some(val));
609            }
610
611            // --- Stack ---
612            Op::Pop => {
613                self.pop(line, col)?;
614            }
615            Op::Dup => {
616                let val = self.peek(line, col)?;
617                self.stack.push(val);
618            }
619
620            // --- Composite types ---
621            Op::BuildList => {
622                let count = chunk.read_u16(self.ip) as usize;
623                self.ip += 2;
624                let start = self.stack.len() - count;
625                let items: Vec<Value> = self.stack.drain(start..).collect();
626                self.stack.push(Value::List(items));
627            }
628            Op::BuildTuple => {
629                let count = chunk.read_u16(self.ip) as usize;
630                self.ip += 2;
631                let start = self.stack.len() - count;
632                let items: Vec<Value> = self.stack.drain(start..).collect();
633                self.stack.push(Value::Tuple(items));
634            }
635            Op::BuildDict => {
636                let count = chunk.read_u16(self.ip) as usize;
637                self.ip += 2;
638                let mut map = IndexMap::new();
639                // Pop count key-value pairs (pushed in order)
640                let start = self.stack.len() - count * 2;
641                let items: Vec<Value> = self.stack.drain(start..).collect();
642                for pair in items.chunks(2) {
643                    let key = match &pair[0] {
644                        Value::Str(s) => s.clone(),
645                        other => other.to_string(),
646                    };
647                    map.insert(key, pair[1].clone());
648                }
649                self.stack.push(Value::Dict(map));
650            }
651
652            // --- Field/index access ---
653            Op::GetField => {
654                let field_idx = chunk.read_u16(self.ip) as usize;
655                self.ip += 2;
656                let field = self.const_as_str(&chunk.constants[field_idx], line, col)?;
657                let obj = self.pop(line, col)?;
658                self.stack.push(self.get_field(obj, &field, line, col)?);
659            }
660            Op::GetIndex => {
661                let index = self.pop(line, col)?;
662                let obj = self.pop(line, col)?;
663                self.stack.push(self.get_index(obj, index, line, col)?);
664            }
665            Op::SetField => {
666                let field_idx = chunk.read_u16(self.ip) as usize;
667                self.ip += 2;
668                let field = self.const_as_str(&chunk.constants[field_idx], line, col)?;
669                let value = self.pop(line, col)?;
670                let obj = self.pop(line, col)?;
671                let result = self.set_field(obj, &field, value, line, col)?;
672                self.stack.push(result);
673            }
674            Op::SetIndex => {
675                let value = self.pop(line, col)?;
676                let index = self.pop(line, col)?;
677                let obj = self.pop(line, col)?;
678                let result = self.set_index(obj, index, value, line, col)?;
679                self.stack.push(result);
680            }
681            Op::MethodCall => {
682                let method_idx = chunk.read_u16(self.ip) as usize;
683                self.ip += 2;
684                let arg_count = chunk.read_u8(self.ip) as usize;
685                self.ip += 1;
686                let method = self.const_as_str(&chunk.constants[method_idx], line, col)?;
687                // Stack: [..., receiver, arg0, arg1, ...]
688                let start = self.stack.len() - arg_count;
689                let args: Vec<Value> = self.stack.drain(start..).collect();
690                let receiver = self.pop(line, col)?;
691                let result = self.call_method(receiver, &method, &args, line, col)?;
692                self.stack.push(result);
693            }
694
695            // --- Closures ---
696            Op::Closure => {
697                let fn_idx = chunk.read_u16(self.ip) as usize;
698                self.ip += 2;
699                let val = chunk.constants[fn_idx].clone();
700                // Attach captures from current env
701                if let Value::Fn(mut f) = val {
702                    f.captures = self.env.capture();
703                    self.stack.push(Value::Fn(f));
704                } else {
705                    self.stack.push(val);
706                }
707            }
708
709            // --- Option/Result ---
710            Op::WrapSome => {
711                let val = self.pop(line, col)?;
712                self.stack.push(Value::Option(Some(Box::new(val))));
713            }
714            Op::WrapOk => {
715                let val = self.pop(line, col)?;
716                self.stack.push(Value::Result(Ok(Box::new(val))));
717            }
718            Op::WrapErr => {
719                let val = self.pop(line, col)?;
720                self.stack.push(Value::Result(Err(Box::new(val))));
721            }
722            Op::Try => {
723                let val = self.pop(line, col)?;
724                match val {
725                    Value::Option(Some(v)) => self.stack.push(*v),
726                    Value::Option(None) => {
727                        return Err(IonError::propagated_none(line, 0));
728                    }
729                    Value::Result(Ok(v)) => self.stack.push(*v),
730                    Value::Result(Err(e)) => {
731                        return Err(IonError::propagated_err(e.to_string(), line, col));
732                    }
733                    other => {
734                        return Err(IonError::type_err(
735                            format!(
736                                "{}{}",
737                                ion_str!("? operator requires Option or Result, got "),
738                                other.type_name()
739                            ),
740                            line,
741                            col,
742                        ));
743                    }
744                }
745            }
746
747            // --- Scope ---
748            Op::PushScope => {
749                self.env.push_scope();
750                self.local_frames.push(self.locals.len());
751            }
752            Op::PopScope => {
753                self.env.pop_scope();
754                if let Some(base) = self.local_frames.pop() {
755                    self.locals.truncate(base);
756                }
757            }
758
759            // --- String ---
760            Op::BuildFString => {
761                let count = chunk.read_u16(self.ip) as usize;
762                self.ip += 2;
763                let start = self.stack.len() - count;
764                let parts: Vec<Value> = self.stack.drain(start..).collect();
765                let mut s = String::with_capacity(count * 8);
766                for part in &parts {
767                    use std::fmt::Write;
768                    let _ = write!(s, "{}", part);
769                }
770                self.stack.push(Value::Str(s));
771            }
772
773            // --- Pipe ---
774            Op::Pipe => {
775                let _arg_count = chunk.read_u8(self.ip);
776                self.ip += 1;
777                // Pipe is handled by the compiler rewriting to Call
778                return Err(IonError::runtime(
779                    ion_str!("pipe opcode should not be executed directly"),
780                    line,
781                    col,
782                ));
783            }
784
785            // --- Pattern matching ---
786            Op::MatchBegin => {
787                // u8: kind (1=Some, 2=Ok, 3=Err, 4=Tuple, 5=List, 6=HostStruct, 7=HostEnum)
788                let kind = chunk.read_u8(self.ip);
789                self.ip += 1;
790                let val = self.pop(line, col)?;
791                let result = match kind {
792                    1 => matches!(val, Value::Option(Some(_))),
793                    2 => matches!(val, Value::Result(Ok(_))),
794                    3 => matches!(val, Value::Result(Err(_))),
795                    4 => {
796                        let expected_len = chunk.read_u8(self.ip) as usize;
797                        self.ip += 1;
798                        match &val {
799                            Value::Tuple(items) => items.len() == expected_len,
800                            _ => false,
801                        }
802                    }
803                    5 => {
804                        let min_len = chunk.read_u8(self.ip) as usize;
805                        self.ip += 1;
806                        let has_rest = chunk.read_u8(self.ip) != 0;
807                        self.ip += 1;
808                        match &val {
809                            Value::List(items) => {
810                                if has_rest {
811                                    items.len() >= min_len
812                                } else {
813                                    items.len() == min_len
814                                }
815                            }
816                            _ => false,
817                        }
818                    }
819                    6 => {
820                        let type_idx = chunk.read_u16(self.ip) as usize;
821                        self.ip += 2;
822                        let expected = chunk
823                            .constants
824                            .get(type_idx)
825                            .ok_or_else(|| {
826                                IonError::runtime(
827                                    ion_str!("type constant index out of bounds"),
828                                    line,
829                                    col,
830                                )
831                            })
832                            .and_then(|value| self.const_as_str(value, line, col))?;
833                        matches!(&val, Value::HostStruct { type_name, .. } if type_name == &expected)
834                    }
835                    7 => {
836                        let enum_idx = chunk.read_u16(self.ip) as usize;
837                        self.ip += 2;
838                        let variant_idx = chunk.read_u16(self.ip) as usize;
839                        self.ip += 2;
840                        let expected_arity = chunk.read_u8(self.ip) as usize;
841                        self.ip += 1;
842                        let expected_enum = chunk
843                            .constants
844                            .get(enum_idx)
845                            .ok_or_else(|| {
846                                IonError::runtime(
847                                    ion_str!("enum constant index out of bounds"),
848                                    line,
849                                    col,
850                                )
851                            })
852                            .and_then(|value| self.const_as_str(value, line, col))?;
853                        let expected_variant = chunk
854                            .constants
855                            .get(variant_idx)
856                            .ok_or_else(|| {
857                                IonError::runtime(
858                                    ion_str!("variant constant index out of bounds"),
859                                    line,
860                                    col,
861                                )
862                            })
863                            .and_then(|value| self.const_as_str(value, line, col))?;
864                        matches!(
865                            &val,
866                            Value::HostEnum {
867                                enum_name,
868                                variant,
869                                data,
870                            } if enum_name == &expected_enum
871                                && variant == &expected_variant
872                                && data.len() == expected_arity
873                        )
874                    }
875                    _ => false,
876                };
877                // Push value back (needed for unwrap) and then bool
878                self.stack.push(val);
879                self.stack.push(Value::Bool(result));
880            }
881            Op::MatchArm => {
882                // u8: kind (1=unwrap Some, 2=unwrap Ok, 3=unwrap Err, 4=get tuple element, 5=get list element, 6=get struct field, 7=get enum data)
883                let kind = chunk.read_u8(self.ip);
884                self.ip += 1;
885                match kind {
886                    1 => {
887                        // Unwrap Some: pop Option(Some(v)), push v
888                        let val = self.pop(line, col)?;
889                        match val {
890                            Value::Option(Some(v)) => self.stack.push(*v),
891                            other => self.stack.push(other),
892                        }
893                    }
894                    2 => {
895                        let val = self.pop(line, col)?;
896                        match val {
897                            Value::Result(Ok(v)) => self.stack.push(*v),
898                            other => self.stack.push(other),
899                        }
900                    }
901                    3 => {
902                        let val = self.pop(line, col)?;
903                        match val {
904                            Value::Result(Err(v)) => self.stack.push(*v),
905                            other => self.stack.push(other),
906                        }
907                    }
908                    4 | 5 => {
909                        // Get tuple/list element: u8 index follows
910                        let idx = chunk.read_u8(self.ip) as usize;
911                        self.ip += 1;
912                        let val = self.peek(line, col)?;
913                        match val {
914                            Value::Tuple(items) | Value::List(items) => {
915                                self.stack
916                                    .push(items.get(idx).cloned().unwrap_or(Value::Unit));
917                            }
918                            _ => self.stack.push(Value::Unit),
919                        }
920                    }
921                    6 => {
922                        let field_idx = chunk.read_u16(self.ip) as usize;
923                        self.ip += 2;
924                        let field = chunk
925                            .constants
926                            .get(field_idx)
927                            .ok_or_else(|| {
928                                IonError::runtime(
929                                    ion_str!("field constant index out of bounds"),
930                                    line,
931                                    col,
932                                )
933                            })
934                            .and_then(|value| self.const_as_str(value, line, col))?;
935                        let val = self.peek(line, col)?;
936                        match val {
937                            Value::HostStruct { fields, .. } => {
938                                let field_value = fields.get(&field).cloned();
939                                self.stack.push(Value::Option(field_value.map(Box::new)));
940                            }
941                            _ => self.stack.push(Value::Option(None)),
942                        }
943                    }
944                    7 => {
945                        let idx = chunk.read_u8(self.ip) as usize;
946                        self.ip += 1;
947                        let val = self.peek(line, col)?;
948                        match val {
949                            Value::HostEnum { data, .. } => {
950                                self.stack
951                                    .push(data.get(idx).cloned().unwrap_or(Value::Unit));
952                            }
953                            _ => self.stack.push(Value::Unit),
954                        }
955                    }
956                    _ => {}
957                }
958            }
959            Op::MatchEnd => {
960                return Err(IonError::runtime(
961                    ion_str!("non-exhaustive match").to_string(),
962                    line,
963                    col,
964                ));
965            }
966
967            // --- Range ---
968            Op::BuildRange => {
969                let inclusive = chunk.read_u8(self.ip) != 0;
970                self.ip += 1;
971                let end = self.pop(line, col)?;
972                let start = self.pop(line, col)?;
973                let s = start.as_int().ok_or_else(|| {
974                    IonError::type_err(ion_str!("range start must be int"), line, col)
975                })?;
976                let e = end.as_int().ok_or_else(|| {
977                    IonError::type_err(ion_str!("range end must be int"), line, col)
978                })?;
979                self.stack.push(Value::Range {
980                    start: s,
981                    end: e,
982                    inclusive,
983                });
984            }
985
986            // --- Host types ---
987            Op::ConstructStruct => {
988                let type_name_idx = chunk.read_u16(self.ip) as usize;
989                self.ip += 2;
990                let raw_count = chunk.read_u16(self.ip) as usize;
991                self.ip += 2;
992                let type_name = match &chunk.constants[type_name_idx] {
993                    Value::Str(s) => s.clone(),
994                    _ => return Err(IonError::runtime(ion_str!("invalid type name"), line, col)),
995                };
996                let has_spread = raw_count & 0x8000 != 0;
997                let field_count = raw_count & 0x7FFF;
998                let mut fields = IndexMap::new();
999                if has_spread {
1000                    // Stack: [..., spread_struct, field_name, field_value, ...]
1001                    // Pop override fields first
1002                    let override_start = self.stack.len() - field_count * 2;
1003                    let overrides: Vec<Value> = self.stack.drain(override_start..).collect();
1004                    // Pop spread struct
1005                    let spread_val = self.pop(line, col)?;
1006                    match spread_val {
1007                        Value::HostStruct { fields: sf, .. } => {
1008                            for (k, v) in sf {
1009                                fields.insert(k, v);
1010                            }
1011                        }
1012                        _ => {
1013                            return Err(IonError::type_err(
1014                                ion_str!("spread in struct constructor requires a struct"),
1015                                line,
1016                                col,
1017                            ))
1018                        }
1019                    }
1020                    // Apply overrides
1021                    for pair in overrides.chunks(2) {
1022                        let fname = match &pair[0] {
1023                            Value::Str(s) => s.clone(),
1024                            _ => {
1025                                return Err(IonError::runtime(
1026                                    ion_str!("invalid field name"),
1027                                    line,
1028                                    col,
1029                                ))
1030                            }
1031                        };
1032                        fields.insert(fname, pair[1].clone());
1033                    }
1034                } else {
1035                    // No spread: fields are pushed as name, value pairs
1036                    let start = self.stack.len() - field_count * 2;
1037                    let items: Vec<Value> = self.stack.drain(start..).collect();
1038                    for pair in items.chunks(2) {
1039                        let fname = match &pair[0] {
1040                            Value::Str(s) => s.clone(),
1041                            _ => {
1042                                return Err(IonError::runtime(
1043                                    ion_str!("invalid field name"),
1044                                    line,
1045                                    col,
1046                                ))
1047                            }
1048                        };
1049                        fields.insert(fname, pair[1].clone());
1050                    }
1051                }
1052                match self.types.construct_struct(&type_name, fields) {
1053                    Ok(val) => self.stack.push(val),
1054                    Err(msg) => return Err(IonError::runtime(msg, line, col)),
1055                }
1056            }
1057            Op::ConstructEnum => {
1058                let enum_name_idx = chunk.read_u16(self.ip) as usize;
1059                self.ip += 2;
1060                let variant_name_idx = chunk.read_u16(self.ip) as usize;
1061                self.ip += 2;
1062                let arg_count = chunk.read_u8(self.ip) as usize;
1063                self.ip += 1;
1064                let enum_name = match &chunk.constants[enum_name_idx] {
1065                    Value::Str(s) => s.clone(),
1066                    _ => return Err(IonError::runtime(ion_str!("invalid enum name"), line, col)),
1067                };
1068                let variant_name = match &chunk.constants[variant_name_idx] {
1069                    Value::Str(s) => s.clone(),
1070                    _ => {
1071                        return Err(IonError::runtime(
1072                            ion_str!("invalid variant name"),
1073                            line,
1074                            col,
1075                        ))
1076                    }
1077                };
1078                let start = self.stack.len() - arg_count;
1079                let args: Vec<Value> = self.stack.drain(start..).collect();
1080                match self.types.construct_enum(&enum_name, &variant_name, args) {
1081                    Ok(val) => self.stack.push(val),
1082                    Err(msg) => return Err(IonError::runtime(msg, line, col)),
1083                }
1084            }
1085
1086            // --- Comprehensions ---
1087            Op::IterInit => {
1088                let val = self.pop(line, col)?;
1089                let iter: Box<dyn Iterator<Item = Value>> = match val {
1090                    Value::List(items) => Box::new(items.into_iter()),
1091                    Value::Set(items) => Box::new(items.into_iter()),
1092                    Value::Tuple(items) => Box::new(items.into_iter()),
1093                    Value::Dict(map) => Box::new(
1094                        map.into_iter()
1095                            .map(|(k, v)| Value::Tuple(vec![Value::Str(k), v])),
1096                    ),
1097                    Value::Str(s) => {
1098                        let chars: Vec<Value> =
1099                            s.chars().map(|c| Value::Str(c.to_string())).collect();
1100                        Box::new(chars.into_iter())
1101                    }
1102                    Value::Bytes(bytes) => {
1103                        let vals: Vec<Value> =
1104                            bytes.into_iter().map(|b| Value::Int(b as i64)).collect();
1105                        Box::new(vals.into_iter())
1106                    }
1107                    Value::Range {
1108                        start,
1109                        end,
1110                        inclusive,
1111                    } => {
1112                        if inclusive {
1113                            Box::new((start..=end).map(Value::Int))
1114                        } else {
1115                            // Use RangeInclusive with adjusted end to avoid two different types
1116                            if end > start {
1117                                Box::new((start..=(end - 1)).map(Value::Int))
1118                            } else {
1119                                Box::new(std::iter::empty())
1120                            }
1121                        }
1122                    }
1123                    other => {
1124                        return Err(IonError::type_err(
1125                            format!("{}{}", ion_str!("cannot iterate over "), other.type_name()),
1126                            line,
1127                            col,
1128                        ));
1129                    }
1130                };
1131                self.iterators.push(iter);
1132                // Push a placeholder on stack so IterNext has something
1133                self.stack.push(Value::Unit);
1134            }
1135            Op::IterNext => {
1136                let offset = chunk.read_u16(self.ip) as usize;
1137                self.ip += 2;
1138                // Pop the previous iteration value/placeholder
1139                self.pop(line, col)?;
1140                let iter = self
1141                    .iterators
1142                    .last_mut()
1143                    .ok_or_else(|| IonError::runtime(ion_str!("no active iterator"), line, col))?;
1144                match iter.next() {
1145                    Some(val) => {
1146                        self.stack.push(val);
1147                    }
1148                    None => {
1149                        self.iterators.pop();
1150                        self.stack.push(Value::Unit); // placeholder for the pop after loop
1151                        self.ip += offset;
1152                    }
1153                }
1154            }
1155            Op::IterDrop => {
1156                self.iterators.pop();
1157            }
1158            Op::ListAppend => {
1159                // Stack: [..., list, iter_placeholder, ..., item]
1160                // Pop item, find the list deeper in the stack, append to it
1161                let item = self.pop(line, col)?;
1162                // Find the list — it's below the iterator placeholder
1163                // The list is at position: stack.len() - 1 (after popping item) minus
1164                // however many scope vars are between. Actually, the list is always
1165                // 2 below the current top: [..., list, iter_placeholder, ...]
1166                // But with scopes, it's simpler to find the last List on the stack.
1167                // Actually: stack layout is [..., list, Unit(iter_placeholder), ...]
1168                // Let's find the list by scanning backwards
1169                let mut found = false;
1170                for i in (0..self.stack.len()).rev() {
1171                    if let Value::List(_) = &self.stack[i] {
1172                        if let Value::List(ref mut items) = self.stack[i] {
1173                            items.push(item.clone());
1174                        }
1175                        found = true;
1176                        break;
1177                    }
1178                }
1179                if !found {
1180                    return Err(IonError::runtime(
1181                        ion_str!("ListAppend: no list on stack"),
1182                        line,
1183                        col,
1184                    ));
1185                }
1186            }
1187            Op::ListExtend => {
1188                // Stack: [..., target_list, source_list]
1189                let source = self.pop(line, col)?;
1190                match source {
1191                    Value::List(other) => {
1192                        let mut found = false;
1193                        for i in (0..self.stack.len()).rev() {
1194                            if let Value::List(ref mut items) = self.stack[i] {
1195                                items.extend(other);
1196                                found = true;
1197                                break;
1198                            }
1199                        }
1200                        if !found {
1201                            return Err(IonError::runtime(
1202                                ion_str!("ListExtend: no list on stack"),
1203                                line,
1204                                col,
1205                            ));
1206                        }
1207                    }
1208                    other => {
1209                        return Err(IonError::type_err(
1210                            format!(
1211                                "{}{}",
1212                                ion_str!("spread requires a list, got "),
1213                                other.type_name()
1214                            ),
1215                            line,
1216                            col,
1217                        ));
1218                    }
1219                }
1220            }
1221            Op::DictInsert => {
1222                // Stack: [..., dict, iter_placeholder, ..., key, value]
1223                let value = self.pop(line, col)?;
1224                let key = self.pop(line, col)?;
1225                let key_str = match key {
1226                    Value::Str(s) => s,
1227                    other => other.to_string(),
1228                };
1229                let mut found = false;
1230                for i in (0..self.stack.len()).rev() {
1231                    if let Value::Dict(_) = &self.stack[i] {
1232                        if let Value::Dict(ref mut map) = self.stack[i] {
1233                            map.insert(key_str.clone(), value.clone());
1234                        }
1235                        found = true;
1236                        break;
1237                    }
1238                }
1239                if !found {
1240                    return Err(IonError::runtime(
1241                        ion_str!("DictInsert: no dict on stack"),
1242                        line,
1243                        col,
1244                    ));
1245                }
1246            }
1247            Op::DictMerge => {
1248                // Stack: [..., target_dict, source_dict]
1249                let source = self.pop(line, col)?;
1250                match source {
1251                    Value::Dict(other) => {
1252                        // Find the target dict on stack
1253                        let mut found = false;
1254                        for i in (0..self.stack.len()).rev() {
1255                            if let Value::Dict(ref mut map) = self.stack[i] {
1256                                for (k, v) in other {
1257                                    map.insert(k, v);
1258                                }
1259                                found = true;
1260                                break;
1261                            }
1262                        }
1263                        if !found {
1264                            return Err(IonError::runtime(
1265                                ion_str!("DictMerge: no dict on stack"),
1266                                line,
1267                                col,
1268                            ));
1269                        }
1270                    }
1271                    _ => {
1272                        return Err(IonError::type_err(
1273                            ion_str!("spread requires a dict").to_string(),
1274                            line,
1275                            col,
1276                        ))
1277                    }
1278                }
1279            }
1280
1281            // --- Slice ---
1282            Op::CheckType => {
1283                let idx = chunk.read_u16(self.ip) as usize;
1284                self.ip += 2;
1285                let type_name = match &chunk.constants[idx] {
1286                    Value::Str(s) => s.clone(),
1287                    _ => unreachable!(),
1288                };
1289                // Peek at TOS without popping
1290                let val = self.stack.last().ok_or_else(|| {
1291                    IonError::runtime(ion_str!("CheckType: empty stack"), line, col)
1292                })?;
1293                let ok = match type_name.as_str() {
1294                    "int" => matches!(val, Value::Int(_)),
1295                    "float" => matches!(val, Value::Float(_)),
1296                    "bool" => matches!(val, Value::Bool(_)),
1297                    "string" => matches!(val, Value::Str(_)),
1298                    "bytes" => matches!(val, Value::Bytes(_)),
1299                    "list" => matches!(val, Value::List(_)),
1300                    "dict" => matches!(val, Value::Dict(_)),
1301                    "tuple" => matches!(val, Value::Tuple(_)),
1302                    "set" => matches!(val, Value::Set(_)),
1303                    "fn" => match val {
1304                        Value::Fn(_) | Value::BuiltinFn(_, _) | Value::BuiltinClosure(_, _) => true,
1305                        #[cfg(feature = "async-runtime")]
1306                        Value::AsyncBuiltinClosure(_, _) => true,
1307                        _ => false,
1308                    },
1309                    "cell" => matches!(val, Value::Cell(_)),
1310                    "any" => true,
1311                    s if s.starts_with("Option") => matches!(val, Value::Option(_)),
1312                    s if s.starts_with("Result") => matches!(val, Value::Result(_)),
1313                    s if s.starts_with("list<") => matches!(val, Value::List(_)),
1314                    s if s.starts_with("dict<") => matches!(val, Value::Dict(_)),
1315                    _ => true,
1316                };
1317                if !ok {
1318                    return Err(IonError::type_err(
1319                        format!(
1320                            "{}{}, {}{}",
1321                            ion_str!("type mismatch: expected "),
1322                            type_name,
1323                            ion_str!("got "),
1324                            val.type_name()
1325                        ),
1326                        line,
1327                        col,
1328                    ));
1329                }
1330            }
1331            Op::Slice => {
1332                let flags = chunk.read_u8(self.ip);
1333                self.ip += 1;
1334                let has_start = flags & 1 != 0;
1335                let has_end = flags & 2 != 0;
1336                let inclusive = flags & 4 != 0;
1337                let end_val = if has_end {
1338                    Some(self.pop(line, col)?)
1339                } else {
1340                    None
1341                };
1342                let start_val = if has_start {
1343                    Some(self.pop(line, col)?)
1344                } else {
1345                    None
1346                };
1347                let obj = self.pop(line, col)?;
1348                let result = self.slice_access(obj, start_val, end_val, inclusive, line, col)?;
1349                self.stack.push(result);
1350            }
1351
1352            Op::SpawnCall => {
1353                let _arg_count = chunk.read_u8(self.ip);
1354                self.ip += 1;
1355                return Err(IonError::runtime(
1356                    ion_str!(
1357                        "spawn requires the pollable async runtime; it cannot run in the synchronous VM"
1358                    ),
1359                    line,
1360                    col,
1361                ));
1362            }
1363            Op::SpawnCallNamed => {
1364                let _arg_count = chunk.read_u8(self.ip) as usize;
1365                self.ip += 1;
1366                let named_count = chunk.read_u8(self.ip) as usize;
1367                self.ip += 1 + named_count * 3;
1368                return Err(IonError::runtime(
1369                    ion_str!(
1370                        "spawn requires the pollable async runtime; it cannot run in the synchronous VM"
1371                    ),
1372                    line,
1373                    col,
1374                ));
1375            }
1376            Op::AwaitTask => {
1377                return Err(IonError::runtime(
1378                    ion_str!(
1379                        "await requires the pollable async runtime; it cannot run in the synchronous VM"
1380                    ),
1381                    line,
1382                    col,
1383                ));
1384            }
1385            Op::SelectTasks => {
1386                let _branch_count = chunk.read_u8(self.ip);
1387                self.ip += 1;
1388                return Err(IonError::runtime(
1389                    ion_str!(
1390                        "select requires the pollable async runtime; it cannot run in the synchronous VM"
1391                    ),
1392                    line,
1393                    col,
1394                ));
1395            }
1396
1397            // --- Print ---
1398            Op::Print => {
1399                let newline = chunk.read_u8(self.ip) != 0;
1400                self.ip += 1;
1401                let val = self.pop(line, col)?;
1402                if newline {
1403                    let text = format!("{}\n", val);
1404                    self.output
1405                        .write(OutputStream::Stdout, &text)
1406                        .map_err(|e| IonError::runtime(e, line, col))?;
1407                } else {
1408                    let text = val.to_string();
1409                    self.output
1410                        .write(OutputStream::Stdout, &text)
1411                        .map_err(|e| IonError::runtime(e, line, col))?;
1412                }
1413                self.stack.push(Value::Unit);
1414            }
1415
1416            Op::TryBegin | Op::TryEnd => {
1417                // Handled in run_chunk before dispatch
1418                unreachable!()
1419            }
1420        }
1421        Ok(None)
1422    }
1423
1424    // ---- Helpers ----
1425
1426    fn decode_op(&self, byte: u8, line: usize, col: usize) -> Result<Op, IonError> {
1427        if byte > Op::Print as u8 {
1428            return Err(IonError::runtime(
1429                format!("{}{}", ion_str!("invalid opcode: "), byte),
1430                line,
1431                col,
1432            ));
1433        }
1434        // SAFETY: Op is repr(u8) and we checked the range
1435        Ok(unsafe { std::mem::transmute::<u8, crate::bytecode::Op>(byte) })
1436    }
1437
1438    fn slice_access(
1439        &self,
1440        obj: Value,
1441        start: Option<Value>,
1442        end: Option<Value>,
1443        inclusive: bool,
1444        line: usize,
1445        col: usize,
1446    ) -> Result<Value, IonError> {
1447        let get_idx = |v: Option<Value>, default: i64| -> Result<i64, IonError> {
1448            match v {
1449                Some(Value::Int(n)) => Ok(n),
1450                None => Ok(default),
1451                Some(other) => Err(IonError::type_err(
1452                    format!(
1453                        "{}{}",
1454                        ion_str!("slice index must be int, got "),
1455                        other.type_name()
1456                    ),
1457                    line,
1458                    col,
1459                )),
1460            }
1461        };
1462        match &obj {
1463            Value::List(items) => {
1464                let len = items.len() as i64;
1465                let s = get_idx(start, 0)?.max(0).min(len) as usize;
1466                let e_raw = get_idx(end, len)?;
1467                let e = if inclusive {
1468                    (e_raw + 1).max(0).min(len) as usize
1469                } else {
1470                    e_raw.max(0).min(len) as usize
1471                };
1472                Ok(Value::List(items[s..e].to_vec()))
1473            }
1474            Value::Str(string) => {
1475                let chars: Vec<char> = string.chars().collect();
1476                let len = chars.len() as i64;
1477                let s = get_idx(start, 0)?.max(0).min(len) as usize;
1478                let e_raw = get_idx(end, len)?;
1479                let e = if inclusive {
1480                    (e_raw + 1).max(0).min(len) as usize
1481                } else {
1482                    e_raw.max(0).min(len) as usize
1483                };
1484                Ok(Value::Str(chars[s..e].iter().collect()))
1485            }
1486            Value::Bytes(bytes) => {
1487                let len = bytes.len() as i64;
1488                let s = get_idx(start, 0)?.max(0).min(len) as usize;
1489                let e_raw = get_idx(end, len)?;
1490                let e = if inclusive {
1491                    (e_raw + 1).max(0).min(len) as usize
1492                } else {
1493                    e_raw.max(0).min(len) as usize
1494                };
1495                Ok(Value::Bytes(bytes[s..e].to_vec()))
1496            }
1497            _ => Err(IonError::type_err(
1498                format!("{}{}", ion_str!("cannot slice "), obj.type_name()),
1499                line,
1500                col,
1501            )),
1502        }
1503    }
1504
1505    fn pop(&mut self, line: usize, col: usize) -> Result<Value, IonError> {
1506        self.stack
1507            .pop()
1508            .ok_or_else(|| IonError::runtime(ion_str!("stack underflow"), line, col))
1509    }
1510
1511    fn peek(&self, line: usize, col: usize) -> Result<Value, IonError> {
1512        self.stack
1513            .last()
1514            .cloned()
1515            .ok_or_else(|| IonError::runtime(ion_str!("stack underflow (peek)"), line, col))
1516    }
1517
1518    /// Check if the top of stack is truthy without cloning.
1519    fn is_top_truthy(&self, line: usize, col: usize) -> Result<bool, IonError> {
1520        self.stack
1521            .last()
1522            .map(|v| v.is_truthy())
1523            .ok_or_else(|| IonError::runtime(ion_str!("stack underflow (peek)"), line, col))
1524    }
1525
1526    fn const_as_str(&self, val: &Value, line: usize, col: usize) -> Result<String, IonError> {
1527        match val {
1528            Value::Str(s) => Ok(s.clone()),
1529            _ => Err(IonError::runtime(
1530                ion_str!("expected string constant"),
1531                line,
1532                col,
1533            )),
1534        }
1535    }
1536
1537    /// Resolve a constant pool string to a Symbol, interning it in the env's string pool.
1538    fn const_to_sym(
1539        &mut self,
1540        val: &Value,
1541        line: usize,
1542        col: usize,
1543    ) -> Result<crate::intern::Symbol, IonError> {
1544        match val {
1545            Value::Str(s) => Ok(self.env.intern(s)),
1546            _ => Err(IonError::runtime(
1547                ion_str!("expected string constant"),
1548                line,
1549                col,
1550            )),
1551        }
1552    }
1553
1554    // ---- Arithmetic ----
1555
1556    fn op_add(&self, a: Value, b: Value, line: usize, col: usize) -> Result<Value, IonError> {
1557        match (&a, &b) {
1558            (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x + y)),
1559            (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)),
1560            (Value::Int(x), Value::Float(y)) => Ok(Value::Float(*x as f64 + y)),
1561            (Value::Float(x), Value::Int(y)) => Ok(Value::Float(x + *y as f64)),
1562            (Value::Str(x), Value::Str(y)) => {
1563                let mut s = String::with_capacity(x.len() + y.len());
1564                s.push_str(x);
1565                s.push_str(y);
1566                Ok(Value::Str(s))
1567            }
1568            (Value::List(x), Value::List(y)) => {
1569                let mut r = x.clone();
1570                r.extend(y.clone());
1571                Ok(Value::List(r))
1572            }
1573            (Value::Bytes(x), Value::Bytes(y)) => {
1574                let mut r = x.clone();
1575                r.extend(y);
1576                Ok(Value::Bytes(r))
1577            }
1578            _ => Err(IonError::type_err(
1579                format!(
1580                    "{}{} and {}",
1581                    ion_str!("cannot add "),
1582                    a.type_name(),
1583                    b.type_name()
1584                ),
1585                line,
1586                col,
1587            )),
1588        }
1589    }
1590
1591    fn op_sub(&self, a: Value, b: Value, line: usize, col: usize) -> Result<Value, IonError> {
1592        match (&a, &b) {
1593            (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x - y)),
1594            (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x - y)),
1595            (Value::Int(x), Value::Float(y)) => Ok(Value::Float(*x as f64 - y)),
1596            (Value::Float(x), Value::Int(y)) => Ok(Value::Float(x - *y as f64)),
1597            _ => Err(IonError::type_err(
1598                format!(
1599                    "{}{} from {}",
1600                    ion_str!("cannot subtract "),
1601                    b.type_name(),
1602                    a.type_name()
1603                ),
1604                line,
1605                col,
1606            )),
1607        }
1608    }
1609
1610    fn op_mul(&self, a: Value, b: Value, line: usize, col: usize) -> Result<Value, IonError> {
1611        match (&a, &b) {
1612            (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x * y)),
1613            (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x * y)),
1614            (Value::Int(x), Value::Float(y)) => Ok(Value::Float(*x as f64 * y)),
1615            (Value::Float(x), Value::Int(y)) => Ok(Value::Float(x * *y as f64)),
1616            (Value::Str(s), Value::Int(n)) | (Value::Int(n), Value::Str(s)) => {
1617                Ok(Value::Str(s.repeat(*n as usize)))
1618            }
1619            _ => Err(IonError::type_err(
1620                format!(
1621                    "{}{} and {}",
1622                    ion_str!("cannot multiply "),
1623                    a.type_name(),
1624                    b.type_name()
1625                ),
1626                line,
1627                col,
1628            )),
1629        }
1630    }
1631
1632    fn op_div(&self, a: Value, b: Value, line: usize, col: usize) -> Result<Value, IonError> {
1633        match (&a, &b) {
1634            (Value::Int(_), Value::Int(0)) => {
1635                Err(IonError::runtime(ion_str!("division by zero"), line, col))
1636            }
1637            (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x / y)),
1638            (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x / y)),
1639            (Value::Int(x), Value::Float(y)) => Ok(Value::Float(*x as f64 / y)),
1640            (Value::Float(x), Value::Int(y)) => Ok(Value::Float(x / *y as f64)),
1641            _ => Err(IonError::type_err(
1642                format!(
1643                    "{}{} by {}",
1644                    ion_str!("cannot divide "),
1645                    a.type_name(),
1646                    b.type_name()
1647                ),
1648                line,
1649                col,
1650            )),
1651        }
1652    }
1653
1654    fn op_mod(&self, a: Value, b: Value, line: usize, col: usize) -> Result<Value, IonError> {
1655        match (&a, &b) {
1656            (Value::Int(_), Value::Int(0)) => {
1657                Err(IonError::runtime(ion_str!("modulo by zero"), line, col))
1658            }
1659            (Value::Int(x), Value::Int(y)) => Ok(Value::Int(x % y)),
1660            (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x % y)),
1661            (Value::Int(x), Value::Float(y)) => Ok(Value::Float(*x as f64 % y)),
1662            (Value::Float(x), Value::Int(y)) => Ok(Value::Float(x % *y as f64)),
1663            _ => Err(IonError::type_err(
1664                format!(
1665                    "{}{} by {}",
1666                    ion_str!("cannot modulo "),
1667                    a.type_name(),
1668                    b.type_name()
1669                ),
1670                line,
1671                col,
1672            )),
1673        }
1674    }
1675
1676    fn op_neg(&self, val: Value, line: usize, col: usize) -> Result<Value, IonError> {
1677        match val {
1678            Value::Int(n) => Ok(Value::Int(-n)),
1679            Value::Float(n) => Ok(Value::Float(-n)),
1680            _ => Err(IonError::type_err(
1681                format!("{}{}", ion_str!("cannot negate "), val.type_name()),
1682                line,
1683                col,
1684            )),
1685        }
1686    }
1687
1688    fn compare_lt(&self, a: &Value, b: &Value, line: usize, col: usize) -> Result<bool, IonError> {
1689        match (a, b) {
1690            (Value::Int(x), Value::Int(y)) => Ok(x < y),
1691            (Value::Float(x), Value::Float(y)) => Ok(x < y),
1692            (Value::Int(x), Value::Float(y)) => Ok((*x as f64) < *y),
1693            (Value::Float(x), Value::Int(y)) => Ok(*x < (*y as f64)),
1694            (Value::Str(x), Value::Str(y)) => Ok(x < y),
1695            _ => Err(IonError::type_err(
1696                format!(
1697                    "{}{} and {}",
1698                    ion_str!("cannot compare "),
1699                    a.type_name(),
1700                    b.type_name()
1701                ),
1702                line,
1703                col,
1704            )),
1705        }
1706    }
1707
1708    // ---- Field/Index access ----
1709
1710    fn get_field(
1711        &self,
1712        obj: Value,
1713        field: &str,
1714        line: usize,
1715        col: usize,
1716    ) -> Result<Value, IonError> {
1717        match &obj {
1718            Value::Dict(map) => Ok(match map.get(field) {
1719                Some(v) => v.clone(),
1720                None => Value::Option(None),
1721            }),
1722            Value::HostStruct { fields, .. } => fields.get(field).cloned().ok_or_else(|| {
1723                IonError::runtime(
1724                    format!(
1725                        "{}{}{}",
1726                        ion_str!("field '"),
1727                        field,
1728                        ion_str!("' not found")
1729                    ),
1730                    line,
1731                    col,
1732                )
1733            }),
1734            Value::List(items) => match field {
1735                "len" => Ok(Value::Int(items.len() as i64)),
1736                _ => Err(IonError::runtime(
1737                    format!(
1738                        "{}{}{}",
1739                        ion_str!("list has no field '"),
1740                        field,
1741                        ion_str!("'")
1742                    ),
1743                    line,
1744                    col,
1745                )),
1746            },
1747            Value::Str(s) => match field {
1748                "len" => Ok(Value::Int(s.len() as i64)),
1749                _ => Err(IonError::runtime(
1750                    format!(
1751                        "{}{}{}",
1752                        ion_str!("string has no field '"),
1753                        field,
1754                        ion_str!("'")
1755                    ),
1756                    line,
1757                    col,
1758                )),
1759            },
1760            Value::Tuple(items) => match field {
1761                "len" => Ok(Value::Int(items.len() as i64)),
1762                _ => Err(IonError::runtime(
1763                    format!(
1764                        "{}{}{}",
1765                        ion_str!("tuple has no field '"),
1766                        field,
1767                        ion_str!("'")
1768                    ),
1769                    line,
1770                    col,
1771                )),
1772            },
1773            _ => Err(IonError::type_err(
1774                format!(
1775                    "{}{}{}{}",
1776                    ion_str!("cannot access field '"),
1777                    field,
1778                    ion_str!("' on "),
1779                    obj.type_name()
1780                ),
1781                line,
1782                col,
1783            )),
1784        }
1785    }
1786
1787    fn get_index(
1788        &self,
1789        obj: Value,
1790        index: Value,
1791        line: usize,
1792        col: usize,
1793    ) -> Result<Value, IonError> {
1794        match (&obj, &index) {
1795            (Value::List(items), Value::Int(i)) => {
1796                let idx = if *i < 0 { items.len() as i64 + i } else { *i } as usize;
1797                items.get(idx).cloned().ok_or_else(|| {
1798                    IonError::runtime(
1799                        format!("{}{}{}", ion_str!("index "), i, ion_str!(" out of range")),
1800                        line,
1801                        col,
1802                    )
1803                })
1804            }
1805            (Value::Tuple(items), Value::Int(i)) => {
1806                let idx = if *i < 0 { items.len() as i64 + i } else { *i } as usize;
1807                items.get(idx).cloned().ok_or_else(|| {
1808                    IonError::runtime(
1809                        format!("{}{}{}", ion_str!("index "), i, ion_str!(" out of range")),
1810                        line,
1811                        col,
1812                    )
1813                })
1814            }
1815            (Value::Dict(map), Value::Str(key)) => Ok(match map.get(key) {
1816                Some(v) => v.clone(),
1817                None => Value::Option(None),
1818            }),
1819            (Value::Str(s), Value::Int(i)) => {
1820                let char_count = s.chars().count() as i64;
1821                let idx = if *i < 0 { char_count + i } else { *i } as usize;
1822                s.chars()
1823                    .nth(idx)
1824                    .map(|c| Value::Str(c.to_string()))
1825                    .ok_or_else(|| {
1826                        IonError::runtime(
1827                            format!("{}{}{}", ion_str!("index "), i, ion_str!(" out of range")),
1828                            line,
1829                            col,
1830                        )
1831                    })
1832            }
1833            (Value::Bytes(bytes), Value::Int(i)) => {
1834                let idx = if *i < 0 { bytes.len() as i64 + i } else { *i } as usize;
1835                bytes
1836                    .get(idx)
1837                    .map(|&b| Value::Int(b as i64))
1838                    .ok_or_else(|| {
1839                        IonError::runtime(
1840                            format!("{}{}{}", ion_str!("index "), i, ion_str!(" out of range")),
1841                            line,
1842                            col,
1843                        )
1844                    })
1845            }
1846            _ => Err(IonError::type_err(
1847                format!(
1848                    "{}{}{}{}",
1849                    ion_str!("cannot index "),
1850                    obj.type_name(),
1851                    ion_str!(" with "),
1852                    index.type_name()
1853                ),
1854                line,
1855                col,
1856            )),
1857        }
1858    }
1859
1860    /// Set index on a container, returning the modified container.
1861    fn set_index(
1862        &self,
1863        obj: Value,
1864        index: Value,
1865        value: Value,
1866        line: usize,
1867        col: usize,
1868    ) -> Result<Value, IonError> {
1869        match (obj, &index) {
1870            (Value::List(mut items), Value::Int(i)) => {
1871                let idx = if *i < 0 { items.len() as i64 + i } else { *i } as usize;
1872                if idx >= items.len() {
1873                    return Err(IonError::runtime(
1874                        format!("{}{}{}", ion_str!("index "), i, ion_str!(" out of range")),
1875                        line,
1876                        col,
1877                    ));
1878                }
1879                items[idx] = value;
1880                Ok(Value::List(items))
1881            }
1882            (Value::Dict(mut map), Value::Str(key)) => {
1883                map.insert(key.clone(), value);
1884                Ok(Value::Dict(map))
1885            }
1886            (obj, _) => Err(IonError::type_err(
1887                format!("{}{}", ion_str!("cannot set index on "), obj.type_name()),
1888                line,
1889                col,
1890            )),
1891        }
1892    }
1893
1894    /// Set field on an object, returning the modified object.
1895    fn set_field(
1896        &self,
1897        obj: Value,
1898        field: &str,
1899        value: Value,
1900        line: usize,
1901        col: usize,
1902    ) -> Result<Value, IonError> {
1903        match obj {
1904            Value::Dict(mut map) => {
1905                map.insert(field.to_string(), value);
1906                Ok(Value::Dict(map))
1907            }
1908            Value::HostStruct {
1909                type_name,
1910                mut fields,
1911            } => {
1912                if fields.contains_key(field) {
1913                    fields.insert(field.to_string(), value);
1914                    Ok(Value::HostStruct { type_name, fields })
1915                } else {
1916                    Err(IonError::runtime(
1917                        format!(
1918                            "{}{}{}{}",
1919                            ion_str!("field '"),
1920                            field,
1921                            ion_str!("' not found on "),
1922                            type_name
1923                        ),
1924                        line,
1925                        col,
1926                    ))
1927                }
1928            }
1929            _ => Err(IonError::type_err(
1930                format!("{}{}", ion_str!("cannot set field on "), obj.type_name()),
1931                line,
1932                col,
1933            )),
1934        }
1935    }
1936
1937    // ---- Method calls ----
1938
1939    fn call_method(
1940        &mut self,
1941        receiver: Value,
1942        method: &str,
1943        args: &[Value],
1944        line: usize,
1945        col: usize,
1946    ) -> Result<Value, IonError> {
1947        // Universal methods available on all types
1948        if method == "to_string" {
1949            return Ok(Value::Str(format!("{}", receiver)));
1950        }
1951        // Handle closure-based methods that need &mut self for invoke_value
1952        match (&receiver, method) {
1953            // List closure methods
1954            (Value::List(items), "map") => {
1955                let func = args.first().ok_or_else(|| {
1956                    IonError::runtime(ion_str!("map requires a function argument"), line, col)
1957                })?;
1958                let mut result = Vec::new();
1959                for item in items {
1960                    result.push(self.invoke_value(func, std::slice::from_ref(item), line, col)?);
1961                }
1962                return Ok(Value::List(result));
1963            }
1964            (Value::List(items), "filter") => {
1965                let func = args.first().ok_or_else(|| {
1966                    IonError::runtime(ion_str!("filter requires a function argument"), line, col)
1967                })?;
1968                let mut result = Vec::new();
1969                for item in items {
1970                    let keep = self.invoke_value(func, std::slice::from_ref(item), line, col)?;
1971                    if keep.is_truthy() {
1972                        result.push(item.clone());
1973                    }
1974                }
1975                return Ok(Value::List(result));
1976            }
1977            (Value::List(items), "fold") => {
1978                let init = args.first().cloned().unwrap_or(Value::Unit);
1979                let func = args.get(1).ok_or_else(|| {
1980                    IonError::runtime(
1981                        ion_str!("fold requires an initial value and a function"),
1982                        line,
1983                        col,
1984                    )
1985                })?;
1986                let mut acc = init;
1987                for item in items {
1988                    acc = self.invoke_value(func, &[acc, item.clone()], line, col)?;
1989                }
1990                return Ok(acc);
1991            }
1992            (Value::List(items), "reduce") => {
1993                if items.is_empty() {
1994                    return Err(IonError::runtime(
1995                        ion_str!("reduce on empty list"),
1996                        line,
1997                        col,
1998                    ));
1999                }
2000                let func = args.first().ok_or_else(|| {
2001                    IonError::runtime(ion_str!("reduce requires a function argument"), line, col)
2002                })?;
2003                let mut acc = items[0].clone();
2004                for item in items.iter().skip(1) {
2005                    acc = self.invoke_value(func, &[acc, item.clone()], line, col)?;
2006                }
2007                return Ok(acc);
2008            }
2009            (Value::List(items), "flat_map") => {
2010                let func = args.first().ok_or_else(|| {
2011                    IonError::runtime(ion_str!("flat_map requires a function argument"), line, col)
2012                })?;
2013                let mut result = Vec::new();
2014                for item in items {
2015                    let mapped = self.invoke_value(func, std::slice::from_ref(item), line, col)?;
2016                    match mapped {
2017                        Value::List(sub) => result.extend(sub),
2018                        other => result.push(other),
2019                    }
2020                }
2021                return Ok(Value::List(result));
2022            }
2023            (Value::List(items), "any") => {
2024                let func = args.first().ok_or_else(|| {
2025                    IonError::runtime(ion_str!("any requires a function argument"), line, col)
2026                })?;
2027                for item in items {
2028                    if self
2029                        .invoke_value(func, std::slice::from_ref(item), line, col)?
2030                        .is_truthy()
2031                    {
2032                        return Ok(Value::Bool(true));
2033                    }
2034                }
2035                return Ok(Value::Bool(false));
2036            }
2037            (Value::List(items), "all") => {
2038                let func = args.first().ok_or_else(|| {
2039                    IonError::runtime(ion_str!("all requires a function argument"), line, col)
2040                })?;
2041                for item in items {
2042                    if !self
2043                        .invoke_value(func, std::slice::from_ref(item), line, col)?
2044                        .is_truthy()
2045                    {
2046                        return Ok(Value::Bool(false));
2047                    }
2048                }
2049                return Ok(Value::Bool(true));
2050            }
2051            (Value::List(items), "sort_by") => {
2052                let func = args.first().ok_or_else(|| {
2053                    IonError::runtime(ion_str!("sort_by requires a function argument"), line, col)
2054                })?;
2055                let mut result = items.to_vec();
2056                let mut err: Option<IonError> = None;
2057                let func_clone = func.clone();
2058                result.sort_by(|a, b| {
2059                    if err.is_some() {
2060                        return std::cmp::Ordering::Equal;
2061                    }
2062                    match self.invoke_value(&func_clone, &[a.clone(), b.clone()], line, col) {
2063                        Ok(Value::Int(n)) => {
2064                            if n < 0 {
2065                                std::cmp::Ordering::Less
2066                            } else if n > 0 {
2067                                std::cmp::Ordering::Greater
2068                            } else {
2069                                std::cmp::Ordering::Equal
2070                            }
2071                        }
2072                        Ok(_) => {
2073                            err = Some(IonError::type_err(
2074                                ion_str!("sort_by function must return int"),
2075                                line,
2076                                col,
2077                            ));
2078                            std::cmp::Ordering::Equal
2079                        }
2080                        Err(e) => {
2081                            err = Some(e);
2082                            std::cmp::Ordering::Equal
2083                        }
2084                    }
2085                });
2086                if let Some(e) = err {
2087                    return Err(e);
2088                }
2089                return Ok(Value::List(result));
2090            }
2091
2092            // Range closure methods — materialize then delegate to list logic
2093            (
2094                Value::Range {
2095                    start,
2096                    end,
2097                    inclusive,
2098                },
2099                "map",
2100            )
2101            | (
2102                Value::Range {
2103                    start,
2104                    end,
2105                    inclusive,
2106                },
2107                "filter",
2108            )
2109            | (
2110                Value::Range {
2111                    start,
2112                    end,
2113                    inclusive,
2114                },
2115                "fold",
2116            )
2117            | (
2118                Value::Range {
2119                    start,
2120                    end,
2121                    inclusive,
2122                },
2123                "reduce",
2124            )
2125            | (
2126                Value::Range {
2127                    start,
2128                    end,
2129                    inclusive,
2130                },
2131                "flat_map",
2132            )
2133            | (
2134                Value::Range {
2135                    start,
2136                    end,
2137                    inclusive,
2138                },
2139                "any",
2140            )
2141            | (
2142                Value::Range {
2143                    start,
2144                    end,
2145                    inclusive,
2146                },
2147                "all",
2148            )
2149            | (
2150                Value::Range {
2151                    start,
2152                    end,
2153                    inclusive,
2154                },
2155                "sort_by",
2156            ) => {
2157                let items = Value::range_to_list(*start, *end, *inclusive);
2158                let list_receiver = Value::List(items);
2159                return self.call_method(list_receiver, method, args, line, col);
2160            }
2161
2162            // Dict closure methods
2163            (Value::Dict(map), "map") => {
2164                let func = args.first().ok_or_else(|| {
2165                    IonError::runtime(ion_str!("map requires a function argument"), line, col)
2166                })?;
2167                let mut result = indexmap::IndexMap::new();
2168                for (k, v) in map {
2169                    let mapped =
2170                        self.invoke_value(func, &[Value::Str(k.clone()), v.clone()], line, col)?;
2171                    result.insert(k.clone(), mapped);
2172                }
2173                return Ok(Value::Dict(result));
2174            }
2175            (Value::Dict(map), "filter") => {
2176                let func = args.first().ok_or_else(|| {
2177                    IonError::runtime(ion_str!("filter requires a function argument"), line, col)
2178                })?;
2179                let mut result = indexmap::IndexMap::new();
2180                for (k, v) in map {
2181                    let keep =
2182                        self.invoke_value(func, &[Value::Str(k.clone()), v.clone()], line, col)?;
2183                    if keep.is_truthy() {
2184                        result.insert(k.clone(), v.clone());
2185                    }
2186                }
2187                return Ok(Value::Dict(result));
2188            }
2189
2190            // Option closure methods
2191            (Value::Option(opt), "map") => {
2192                let func = args.first().ok_or_else(|| {
2193                    IonError::runtime(ion_str!("map requires a function argument"), line, col)
2194                })?;
2195                return match opt {
2196                    Some(v) => {
2197                        let result = self.invoke_value(func, &[*v.clone()], line, col)?;
2198                        Ok(Value::Option(Some(Box::new(result))))
2199                    }
2200                    None => Ok(Value::Option(None)),
2201                };
2202            }
2203            (Value::Option(opt), "and_then") => {
2204                let func = args.first().ok_or_else(|| {
2205                    IonError::runtime(ion_str!("and_then requires a function argument"), line, col)
2206                })?;
2207                return match opt {
2208                    Some(v) => self.invoke_value(func, &[*v.clone()], line, col),
2209                    None => Ok(Value::Option(None)),
2210                };
2211            }
2212            (Value::Option(opt), "or_else") => {
2213                let func = args.first().ok_or_else(|| {
2214                    IonError::runtime(ion_str!("or_else requires a function argument"), line, col)
2215                })?;
2216                return match opt {
2217                    Some(v) => Ok(Value::Option(Some(v.clone()))),
2218                    None => self.invoke_value(func, &[], line, col),
2219                };
2220            }
2221            (Value::Option(opt), "unwrap_or_else") => {
2222                let func = args.first().ok_or_else(|| {
2223                    IonError::runtime(
2224                        ion_str!("unwrap_or_else requires a function argument"),
2225                        line,
2226                        col,
2227                    )
2228                })?;
2229                return match opt {
2230                    Some(v) => Ok(*v.clone()),
2231                    None => self.invoke_value(func, &[], line, col),
2232                };
2233            }
2234
2235            // Result closure methods
2236            (Value::Result(res), "map") => {
2237                let func = args.first().ok_or_else(|| {
2238                    IonError::runtime(ion_str!("map requires a function argument"), line, col)
2239                })?;
2240                return match res {
2241                    Ok(v) => {
2242                        let result = self.invoke_value(func, &[*v.clone()], line, col)?;
2243                        Ok(Value::Result(Ok(Box::new(result))))
2244                    }
2245                    Err(e) => Ok(Value::Result(Err(e.clone()))),
2246                };
2247            }
2248            (Value::Result(res), "map_err") => {
2249                let func = args.first().ok_or_else(|| {
2250                    IonError::runtime(ion_str!("map_err requires a function argument"), line, col)
2251                })?;
2252                return match res {
2253                    Ok(v) => Ok(Value::Result(Ok(v.clone()))),
2254                    Err(e) => {
2255                        let result = self.invoke_value(func, &[*e.clone()], line, col)?;
2256                        Ok(Value::Result(Err(Box::new(result))))
2257                    }
2258                };
2259            }
2260            (Value::Result(res), "and_then") => {
2261                let func = args.first().ok_or_else(|| {
2262                    IonError::runtime(ion_str!("and_then requires a function argument"), line, col)
2263                })?;
2264                return match res {
2265                    Ok(v) => self.invoke_value(func, &[*v.clone()], line, col),
2266                    Err(e) => Ok(Value::Result(Err(e.clone()))),
2267                };
2268            }
2269            (Value::Result(res), "or_else") => {
2270                let func = args.first().ok_or_else(|| {
2271                    IonError::runtime(ion_str!("or_else requires a function argument"), line, col)
2272                })?;
2273                return match res {
2274                    Ok(v) => Ok(Value::Result(Ok(v.clone()))),
2275                    Err(e) => self.invoke_value(func, &[*e.clone()], line, col),
2276                };
2277            }
2278            (Value::Result(res), "unwrap_or_else") => {
2279                let func = args.first().ok_or_else(|| {
2280                    IonError::runtime(
2281                        ion_str!("unwrap_or_else requires a function argument"),
2282                        line,
2283                        col,
2284                    )
2285                })?;
2286                return match res {
2287                    Ok(v) => Ok(*v.clone()),
2288                    Err(e) => self.invoke_value(func, &[*e.clone()], line, col),
2289                };
2290            }
2291
2292            // Cell closure methods
2293            (Value::Cell(cell), "update") => {
2294                let func = args.first().ok_or_else(|| {
2295                    IonError::runtime(
2296                        ion_str!("cell.update() requires a function argument"),
2297                        line,
2298                        col,
2299                    )
2300                })?;
2301                let current = { cell.lock().unwrap().clone() };
2302                let new_val = self.invoke_value(func, &[current], line, col)?;
2303                let mut inner = cell.lock().unwrap();
2304                *inner = new_val.clone();
2305                return Ok(new_val);
2306            }
2307
2308            _ => {}
2309        }
2310
2311        // Non-closure methods
2312        match &receiver {
2313            Value::List(items) => self.list_method(items, method, args, line, col),
2314            Value::Tuple(items) => self.tuple_method(items, method, args, line, col),
2315            Value::Str(s) => self.str_method(s, method, args, line, col),
2316            Value::Dict(map) => self.dict_method(map, method, args, line, col),
2317            Value::Bytes(b) => self.bytes_method(b, method, args, line, col),
2318            Value::Set(items) => self.set_method(items, method, args, line, col),
2319            Value::Option(_) => self.option_method(&receiver, method, args, line, col),
2320            Value::Result(_) => self.result_method(&receiver, method, args, line, col),
2321            Value::Range {
2322                start,
2323                end,
2324                inclusive,
2325            } => match method {
2326                "len" => Ok(Value::Int(Value::range_len(*start, *end, *inclusive))),
2327                "contains" => {
2328                    let val = args[0].as_int().ok_or_else(|| {
2329                        IonError::type_err(ion_str!("range.contains requires int"), line, col)
2330                    })?;
2331                    let in_range = if *inclusive {
2332                        val >= *start && val <= *end
2333                    } else {
2334                        val >= *start && val < *end
2335                    };
2336                    Ok(Value::Bool(in_range))
2337                }
2338                "to_list" => Ok(Value::List(Value::range_to_list(*start, *end, *inclusive))),
2339                _ => {
2340                    let items = Value::range_to_list(*start, *end, *inclusive);
2341                    self.list_method(&items, method, args, line, col)
2342                }
2343            },
2344            Value::Cell(cell) => match method {
2345                "get" => Ok(cell.lock().unwrap().clone()),
2346                "set" => {
2347                    if let Some(val) = args.first() {
2348                        let mut inner = cell.lock().unwrap();
2349                        *inner = val.clone();
2350                        Ok(Value::Unit)
2351                    } else {
2352                        Err(IonError::runtime(
2353                            ion_str!("cell.set() requires 1 argument"),
2354                            line,
2355                            col,
2356                        ))
2357                    }
2358                }
2359                _ => Err(IonError::type_err(
2360                    format!(
2361                        "{}{}{}",
2362                        ion_str!("no method '"),
2363                        method,
2364                        ion_str!("' on cell")
2365                    ),
2366                    line,
2367                    col,
2368                )),
2369            },
2370            _ => Err(IonError::type_err(
2371                format!(
2372                    "{}{}{}{}",
2373                    receiver.type_name(),
2374                    ion_str!(" has no method '"),
2375                    method,
2376                    ion_str!("'")
2377                ),
2378                line,
2379                col,
2380            )),
2381        }
2382    }
2383
2384    fn list_method(
2385        &self,
2386        items: &[Value],
2387        method: &str,
2388        args: &[Value],
2389        line: usize,
2390        col: usize,
2391    ) -> Result<Value, IonError> {
2392        match method {
2393            "len" => Ok(Value::Int(items.len() as i64)),
2394            "push" => {
2395                let mut new = items.to_vec();
2396                for a in args {
2397                    new.push(a.clone());
2398                }
2399                Ok(Value::List(new))
2400            }
2401            "pop" => {
2402                if items.is_empty() {
2403                    Ok(Value::Tuple(vec![Value::List(vec![]), Value::Option(None)]))
2404                } else {
2405                    let mut new = items.to_vec();
2406                    let popped = new.pop().unwrap();
2407                    Ok(Value::Tuple(vec![
2408                        Value::List(new),
2409                        Value::Option(Some(Box::new(popped))),
2410                    ]))
2411                }
2412            }
2413            "contains" => Ok(Value::Bool(
2414                args.first().map(|a| items.contains(a)).unwrap_or(false),
2415            )),
2416            "is_empty" => Ok(Value::Bool(items.is_empty())),
2417            "reverse" => {
2418                let mut new = items.to_vec();
2419                new.reverse();
2420                Ok(Value::List(new))
2421            }
2422            "join" => {
2423                let sep = args.first().and_then(|a| a.as_str()).unwrap_or("");
2424                let s: String = items
2425                    .iter()
2426                    .map(|v| v.to_string())
2427                    .collect::<Vec<_>>()
2428                    .join(sep);
2429                Ok(Value::Str(s))
2430            }
2431            "enumerate" => {
2432                let pairs: Vec<Value> = items
2433                    .iter()
2434                    .enumerate()
2435                    .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v.clone()]))
2436                    .collect();
2437                Ok(Value::List(pairs))
2438            }
2439            "first" => Ok(match items.first() {
2440                Some(v) => Value::Option(Some(Box::new(v.clone()))),
2441                None => Value::Option(None),
2442            }),
2443            "last" => Ok(match items.last() {
2444                Some(v) => Value::Option(Some(Box::new(v.clone()))),
2445                None => Value::Option(None),
2446            }),
2447            "sort" => {
2448                if !items.is_empty() {
2449                    let first_type = std::mem::discriminant(&items[0]);
2450                    for item in items.iter().skip(1) {
2451                        if std::mem::discriminant(item) != first_type {
2452                            return Err(IonError::type_err(
2453                                ion_str!("sort() requires all elements to be the same type"),
2454                                line,
2455                                col,
2456                            ));
2457                        }
2458                    }
2459                }
2460                let mut sorted = items.to_vec();
2461                sorted.sort_by(|a, b| match (a, b) {
2462                    (Value::Int(x), Value::Int(y)) => x.cmp(y),
2463                    (Value::Float(x), Value::Float(y)) => {
2464                        x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal)
2465                    }
2466                    (Value::Str(x), Value::Str(y)) => x.cmp(y),
2467                    _ => std::cmp::Ordering::Equal,
2468                });
2469                Ok(Value::List(sorted))
2470            }
2471            "flatten" => {
2472                let mut result = Vec::new();
2473                for item in items {
2474                    if let Value::List(inner) = item {
2475                        result.extend(inner.iter().cloned());
2476                    } else {
2477                        result.push(item.clone());
2478                    }
2479                }
2480                Ok(Value::List(result))
2481            }
2482            "zip" => {
2483                if let Some(Value::List(other)) = args.first() {
2484                    let result: Vec<Value> = items
2485                        .iter()
2486                        .zip(other.iter())
2487                        .map(|(a, b)| Value::Tuple(vec![a.clone(), b.clone()]))
2488                        .collect();
2489                    Ok(Value::List(result))
2490                } else {
2491                    Err(IonError::type_err(
2492                        ion_str!("zip requires a list argument"),
2493                        line,
2494                        col,
2495                    ))
2496                }
2497            }
2498            "index" => {
2499                let target = args.first().ok_or_else(|| {
2500                    IonError::type_err(ion_str!("index requires an argument"), line, col)
2501                })?;
2502                Ok(match items.iter().position(|v| v == target) {
2503                    Some(i) => Value::Option(Some(Box::new(Value::Int(i as i64)))),
2504                    None => Value::Option(None),
2505                })
2506            }
2507            "count" => {
2508                let target = args.first().ok_or_else(|| {
2509                    IonError::type_err(ion_str!("count requires an argument"), line, col)
2510                })?;
2511                Ok(Value::Int(
2512                    items.iter().filter(|v| *v == target).count() as i64
2513                ))
2514            }
2515            "slice" => {
2516                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
2517                let end = args
2518                    .get(1)
2519                    .and_then(|a| a.as_int())
2520                    .map(|n| n as usize)
2521                    .unwrap_or(items.len());
2522                let start = start.min(items.len());
2523                let end = end.min(items.len());
2524                Ok(Value::List(items[start..end].to_vec()))
2525            }
2526            "dedup" => {
2527                let mut result: Vec<Value> = Vec::new();
2528                for item in items {
2529                    if result.last() != Some(item) {
2530                        result.push(item.clone());
2531                    }
2532                }
2533                Ok(Value::List(result))
2534            }
2535            "unique" => {
2536                let mut seen = Vec::new();
2537                let mut result = Vec::new();
2538                for item in items {
2539                    if !seen.contains(item) {
2540                        seen.push(item.clone());
2541                        result.push(item.clone());
2542                    }
2543                }
2544                Ok(Value::List(result))
2545            }
2546            "min" => {
2547                if items.is_empty() {
2548                    return Ok(Value::Option(None));
2549                }
2550                let mut min = &items[0];
2551                for item in items.iter().skip(1) {
2552                    match (min, item) {
2553                        (Value::Int(a), Value::Int(b)) if b < a => min = item,
2554                        (Value::Float(a), Value::Float(b)) if b < a => min = item,
2555                        (Value::Str(a), Value::Str(b)) if b < a => min = item,
2556                        (Value::Int(_), Value::Int(_))
2557                        | (Value::Float(_), Value::Float(_))
2558                        | (Value::Str(_), Value::Str(_)) => {}
2559                        _ => {
2560                            return Err(IonError::type_err(
2561                                ion_str!("min() requires homogeneous comparable elements"),
2562                                line,
2563                                col,
2564                            ))
2565                        }
2566                    }
2567                }
2568                Ok(Value::Option(Some(Box::new(min.clone()))))
2569            }
2570            "max" => {
2571                if items.is_empty() {
2572                    return Ok(Value::Option(None));
2573                }
2574                let mut max = &items[0];
2575                for item in items.iter().skip(1) {
2576                    match (max, item) {
2577                        (Value::Int(a), Value::Int(b)) if b > a => max = item,
2578                        (Value::Float(a), Value::Float(b)) if b > a => max = item,
2579                        (Value::Str(a), Value::Str(b)) if b > a => max = item,
2580                        (Value::Int(_), Value::Int(_))
2581                        | (Value::Float(_), Value::Float(_))
2582                        | (Value::Str(_), Value::Str(_)) => {}
2583                        _ => {
2584                            return Err(IonError::type_err(
2585                                ion_str!("max() requires homogeneous comparable elements"),
2586                                line,
2587                                col,
2588                            ))
2589                        }
2590                    }
2591                }
2592                Ok(Value::Option(Some(Box::new(max.clone()))))
2593            }
2594            "sum" => {
2595                let mut int_sum: i64 = 0;
2596                let mut float_sum: f64 = 0.0;
2597                let mut has_float = false;
2598                for item in items {
2599                    match item {
2600                        Value::Int(n) => int_sum += n,
2601                        Value::Float(f) => {
2602                            has_float = true;
2603                            float_sum += f;
2604                        }
2605                        _ => {
2606                            return Err(IonError::type_err(
2607                                ion_str!("sum() requires numeric elements"),
2608                                line,
2609                                col,
2610                            ))
2611                        }
2612                    }
2613                }
2614                if has_float {
2615                    Ok(Value::Float(float_sum + int_sum as f64))
2616                } else {
2617                    Ok(Value::Int(int_sum))
2618                }
2619            }
2620            "window" => {
2621                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2622                    IonError::type_err(ion_str!("window requires int argument"), line, col)
2623                })? as usize;
2624                if n == 0 {
2625                    return Err(IonError::runtime(
2626                        ion_str!("window size must be > 0"),
2627                        line,
2628                        col,
2629                    ));
2630                }
2631                let result: Vec<Value> =
2632                    items.windows(n).map(|w| Value::List(w.to_vec())).collect();
2633                Ok(Value::List(result))
2634            }
2635            "chunk" => {
2636                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2637                    IonError::type_err(ion_str!("chunk requires int argument"), line, col)
2638                })? as usize;
2639                if n == 0 {
2640                    return Err(IonError::type_err(
2641                        ion_str!("chunk size must be > 0"),
2642                        line,
2643                        col,
2644                    ));
2645                }
2646                let result: Vec<Value> = items.chunks(n).map(|c| Value::List(c.to_vec())).collect();
2647                Ok(Value::List(result))
2648            }
2649            _ => Err(IonError::type_err(
2650                format!(
2651                    "{}{}{}",
2652                    ion_str!("list has no method '"),
2653                    method,
2654                    ion_str!("'")
2655                ),
2656                line,
2657                col,
2658            )),
2659        }
2660    }
2661
2662    fn set_method(
2663        &self,
2664        items: &[Value],
2665        method: &str,
2666        args: &[Value],
2667        line: usize,
2668        col: usize,
2669    ) -> Result<Value, IonError> {
2670        match method {
2671            "len" => Ok(Value::Int(items.len() as i64)),
2672            "contains" => Ok(Value::Bool(
2673                args.first().map(|a| items.contains(a)).unwrap_or(false),
2674            )),
2675            "is_empty" => Ok(Value::Bool(items.is_empty())),
2676            "add" => {
2677                let val = &args[0];
2678                let mut new = items.to_vec();
2679                if !new.iter().any(|v| v == val) {
2680                    new.push(val.clone());
2681                }
2682                Ok(Value::Set(new))
2683            }
2684            "remove" => {
2685                let val = &args[0];
2686                let new: Vec<Value> = items.iter().filter(|v| *v != val).cloned().collect();
2687                Ok(Value::Set(new))
2688            }
2689            "union" => {
2690                if let Some(Value::Set(other)) = args.first() {
2691                    let mut new = items.to_vec();
2692                    for v in other {
2693                        if !new.iter().any(|x| x == v) {
2694                            new.push(v.clone());
2695                        }
2696                    }
2697                    Ok(Value::Set(new))
2698                } else {
2699                    Err(IonError::type_err(
2700                        ion_str!("union requires a set argument"),
2701                        line,
2702                        col,
2703                    ))
2704                }
2705            }
2706            "intersection" => {
2707                if let Some(Value::Set(other)) = args.first() {
2708                    let new: Vec<Value> = items
2709                        .iter()
2710                        .filter(|v| other.iter().any(|x| x == *v))
2711                        .cloned()
2712                        .collect();
2713                    Ok(Value::Set(new))
2714                } else {
2715                    Err(IonError::type_err(
2716                        ion_str!("intersection requires a set argument"),
2717                        line,
2718                        col,
2719                    ))
2720                }
2721            }
2722            "difference" => {
2723                if let Some(Value::Set(other)) = args.first() {
2724                    let new: Vec<Value> = items
2725                        .iter()
2726                        .filter(|v| !other.iter().any(|x| x == *v))
2727                        .cloned()
2728                        .collect();
2729                    Ok(Value::Set(new))
2730                } else {
2731                    Err(IonError::type_err(
2732                        ion_str!("difference requires a set argument"),
2733                        line,
2734                        col,
2735                    ))
2736                }
2737            }
2738            "to_list" => Ok(Value::List(items.to_vec())),
2739            _ => Err(IonError::type_err(
2740                format!(
2741                    "{}{}{}",
2742                    ion_str!("set has no method '"),
2743                    method,
2744                    ion_str!("'")
2745                ),
2746                line,
2747                col,
2748            )),
2749        }
2750    }
2751
2752    fn tuple_method(
2753        &self,
2754        items: &[Value],
2755        method: &str,
2756        args: &[Value],
2757        line: usize,
2758        col: usize,
2759    ) -> Result<Value, IonError> {
2760        match method {
2761            "len" => Ok(Value::Int(items.len() as i64)),
2762            "contains" => Ok(Value::Bool(
2763                args.first().map(|a| items.contains(a)).unwrap_or(false),
2764            )),
2765            "to_list" => Ok(Value::List(items.to_vec())),
2766            _ => Err(IonError::type_err(
2767                format!(
2768                    "{}{}{}",
2769                    ion_str!("tuple has no method '"),
2770                    method,
2771                    ion_str!("'")
2772                ),
2773                line,
2774                col,
2775            )),
2776        }
2777    }
2778
2779    fn str_method(
2780        &self,
2781        s: &str,
2782        method: &str,
2783        args: &[Value],
2784        line: usize,
2785        col: usize,
2786    ) -> Result<Value, IonError> {
2787        match method {
2788            "len" => Ok(Value::Int(s.len() as i64)),
2789            "to_upper" => Ok(Value::Str(s.to_uppercase())),
2790            "to_lower" => Ok(Value::Str(s.to_lowercase())),
2791            "trim" => Ok(Value::Str(s.trim().to_string())),
2792            "contains" => match args.first() {
2793                Some(Value::Str(sub)) => Ok(Value::Bool(s.contains(sub.as_str()))),
2794                Some(Value::Int(code)) => {
2795                    let ch = char::from_u32(*code as u32).ok_or_else(|| {
2796                        IonError::type_err(ion_str!("invalid char code"), line, col)
2797                    })?;
2798                    Ok(Value::Bool(s.contains(ch)))
2799                }
2800                _ => Err(IonError::type_err(
2801                    ion_str!("contains requires string or int argument"),
2802                    line,
2803                    col,
2804                )),
2805            },
2806            "starts_with" => {
2807                let prefix = args.first().and_then(|a| a.as_str()).unwrap_or("");
2808                Ok(Value::Bool(s.starts_with(prefix)))
2809            }
2810            "ends_with" => {
2811                let suffix = args.first().and_then(|a| a.as_str()).unwrap_or("");
2812                Ok(Value::Bool(s.ends_with(suffix)))
2813            }
2814            "split" => {
2815                let sep = args.first().and_then(|a| a.as_str()).unwrap_or(" ");
2816                let parts: Vec<Value> = s.split(sep).map(|p| Value::Str(p.to_string())).collect();
2817                Ok(Value::List(parts))
2818            }
2819            "replace" => {
2820                let from = args.first().and_then(|a| a.as_str()).unwrap_or("");
2821                let to = args.get(1).and_then(|a| a.as_str()).unwrap_or("");
2822                Ok(Value::Str(s.replace(from, to)))
2823            }
2824            "chars" => {
2825                let chars: Vec<Value> = s.chars().map(|c| Value::Str(c.to_string())).collect();
2826                Ok(Value::List(chars))
2827            }
2828            "char_len" => Ok(Value::Int(s.chars().count() as i64)),
2829            "is_empty" => Ok(Value::Bool(s.is_empty())),
2830            "trim_start" => Ok(Value::Str(s.trim_start().to_string())),
2831            "trim_end" => Ok(Value::Str(s.trim_end().to_string())),
2832            "repeat" => {
2833                let n = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2834                    IonError::type_err(ion_str!("repeat requires int argument"), line, col)
2835                })?;
2836                Ok(Value::Str(s.repeat(n as usize)))
2837            }
2838            "find" => {
2839                let sub = args.first().and_then(|a| a.as_str()).unwrap_or("");
2840                Ok(match s.find(sub) {
2841                    Some(byte_idx) => {
2842                        let char_idx = s[..byte_idx].chars().count();
2843                        Value::Option(Some(Box::new(Value::Int(char_idx as i64))))
2844                    }
2845                    None => Value::Option(None),
2846                })
2847            }
2848            "to_int" => Ok(match s.trim().parse::<i64>() {
2849                std::result::Result::Ok(n) => Value::Result(Ok(Box::new(Value::Int(n)))),
2850                std::result::Result::Err(e) => {
2851                    Value::Result(Err(Box::new(Value::Str(e.to_string()))))
2852                }
2853            }),
2854            "to_float" => Ok(match s.trim().parse::<f64>() {
2855                std::result::Result::Ok(f) => Value::Result(Ok(Box::new(Value::Float(f)))),
2856                std::result::Result::Err(e) => {
2857                    Value::Result(Err(Box::new(Value::Str(e.to_string()))))
2858                }
2859            }),
2860            "bytes" => {
2861                let bytes: Vec<Value> = s.bytes().map(|b| Value::Int(b as i64)).collect();
2862                Ok(Value::List(bytes))
2863            }
2864            "strip_prefix" => {
2865                let pre = args.first().and_then(|a| a.as_str()).unwrap_or("");
2866                Ok(Value::Str(s.strip_prefix(pre).unwrap_or(s).to_string()))
2867            }
2868            "strip_suffix" => {
2869                let suf = args.first().and_then(|a| a.as_str()).unwrap_or("");
2870                Ok(Value::Str(s.strip_suffix(suf).unwrap_or(s).to_string()))
2871            }
2872            "pad_start" => {
2873                let width = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2874                    IonError::type_err(ion_str!("pad_start requires int argument"), line, col)
2875                })? as usize;
2876                let ch = args
2877                    .get(1)
2878                    .and_then(|a| a.as_str())
2879                    .and_then(|s| s.chars().next())
2880                    .unwrap_or(' ');
2881                let char_len = s.chars().count();
2882                if char_len >= width {
2883                    Ok(Value::Str(s.to_string()))
2884                } else {
2885                    let pad: String = std::iter::repeat_n(ch, width - char_len).collect();
2886                    Ok(Value::Str(format!("{}{}", pad, s)))
2887                }
2888            }
2889            "pad_end" => {
2890                let width = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2891                    IonError::type_err(ion_str!("pad_end requires int argument"), line, col)
2892                })? as usize;
2893                let ch = args
2894                    .get(1)
2895                    .and_then(|a| a.as_str())
2896                    .and_then(|s| s.chars().next())
2897                    .unwrap_or(' ');
2898                let char_len = s.chars().count();
2899                if char_len >= width {
2900                    Ok(Value::Str(s.to_string()))
2901                } else {
2902                    let pad: String = std::iter::repeat_n(ch, width - char_len).collect();
2903                    Ok(Value::Str(format!("{}{}", s, pad)))
2904                }
2905            }
2906            "reverse" => Ok(Value::Str(s.chars().rev().collect())),
2907            "slice" => {
2908                let chars: Vec<char> = s.chars().collect();
2909                let char_count = chars.len();
2910                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
2911                let end = args
2912                    .get(1)
2913                    .and_then(|a| a.as_int())
2914                    .map(|n| n as usize)
2915                    .unwrap_or(char_count);
2916                let start = start.min(char_count);
2917                let end = end.min(char_count);
2918                Ok(Value::Str(chars[start..end].iter().collect()))
2919            }
2920            _ => Err(IonError::type_err(
2921                format!(
2922                    "{}{}{}",
2923                    ion_str!("string has no method '"),
2924                    method,
2925                    ion_str!("'")
2926                ),
2927                line,
2928                col,
2929            )),
2930        }
2931    }
2932
2933    fn bytes_method(
2934        &self,
2935        bytes: &[u8],
2936        method: &str,
2937        args: &[Value],
2938        line: usize,
2939        col: usize,
2940    ) -> Result<Value, IonError> {
2941        match method {
2942            "len" => Ok(Value::Int(bytes.len() as i64)),
2943            "is_empty" => Ok(Value::Bool(bytes.is_empty())),
2944            "contains" => {
2945                let byte = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2946                    IonError::type_err(ion_str!("bytes.contains() requires an int"), line, col)
2947                })?;
2948                Ok(Value::Bool(bytes.contains(&(byte as u8))))
2949            }
2950            "slice" => {
2951                let start = args.first().and_then(|a| a.as_int()).unwrap_or(0) as usize;
2952                let end = args
2953                    .get(1)
2954                    .and_then(|a| a.as_int())
2955                    .map(|n| n as usize)
2956                    .unwrap_or(bytes.len());
2957                let start = start.min(bytes.len());
2958                let end = end.min(bytes.len());
2959                Ok(Value::Bytes(bytes[start..end].to_vec()))
2960            }
2961            "to_list" => Ok(Value::List(
2962                bytes.iter().map(|&b| Value::Int(b as i64)).collect(),
2963            )),
2964            "to_str" => match std::str::from_utf8(bytes) {
2965                std::result::Result::Ok(s) => {
2966                    Ok(Value::Result(Ok(Box::new(Value::Str(s.to_string())))))
2967                }
2968                std::result::Result::Err(e) => {
2969                    Ok(Value::Result(Err(Box::new(Value::Str(format!("{}", e))))))
2970                }
2971            },
2972            "to_hex" => {
2973                let hex: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
2974                Ok(Value::Str(hex))
2975            }
2976            "find" => {
2977                let needle = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2978                    IonError::type_err(ion_str!("bytes.find() requires an int"), line, col)
2979                })?;
2980                let pos = bytes.iter().position(|&b| b == needle as u8);
2981                Ok(match pos {
2982                    Some(i) => Value::Option(Some(Box::new(Value::Int(i as i64)))),
2983                    None => Value::Option(None),
2984                })
2985            }
2986            "reverse" => {
2987                let mut rev = bytes.to_vec();
2988                rev.reverse();
2989                Ok(Value::Bytes(rev))
2990            }
2991            "push" => {
2992                let byte = args.first().and_then(|a| a.as_int()).ok_or_else(|| {
2993                    IonError::type_err(ion_str!("bytes.push() requires an int"), line, col)
2994                })?;
2995                let mut new = bytes.to_vec();
2996                new.push(byte as u8);
2997                Ok(Value::Bytes(new))
2998            }
2999            _ => Err(IonError::type_err(
3000                format!(
3001                    "{}{}{}",
3002                    ion_str!("bytes has no method '"),
3003                    method,
3004                    ion_str!("'")
3005                ),
3006                line,
3007                col,
3008            )),
3009        }
3010    }
3011
3012    fn dict_method(
3013        &self,
3014        map: &IndexMap<String, Value>,
3015        method: &str,
3016        args: &[Value],
3017        line: usize,
3018        col: usize,
3019    ) -> Result<Value, IonError> {
3020        match method {
3021            "len" => Ok(Value::Int(map.len() as i64)),
3022            "keys" => {
3023                let keys: Vec<Value> = map.keys().map(|k| Value::Str(k.clone())).collect();
3024                Ok(Value::List(keys))
3025            }
3026            "values" => {
3027                let vals: Vec<Value> = map.values().cloned().collect();
3028                Ok(Value::List(vals))
3029            }
3030            "contains_key" => {
3031                let key = args.first().and_then(|a| a.as_str()).unwrap_or("");
3032                Ok(Value::Bool(map.contains_key(key)))
3033            }
3034            "get" => {
3035                let key = args.first().and_then(|a| a.as_str()).unwrap_or("");
3036                Ok(match map.get(key) {
3037                    Some(v) => Value::Option(Some(Box::new(v.clone()))),
3038                    None => Value::Option(None),
3039                })
3040            }
3041            "is_empty" => Ok(Value::Bool(map.is_empty())),
3042            "entries" => Ok(Value::List(
3043                map.iter()
3044                    .map(|(k, v)| Value::Tuple(vec![Value::Str(k.clone()), v.clone()]))
3045                    .collect(),
3046            )),
3047            "insert" => {
3048                let key = args.first().and_then(|a| a.as_str()).unwrap_or("");
3049                let val = args.get(1).cloned().unwrap_or(Value::Unit);
3050                let mut new_map = map.clone();
3051                new_map.insert(key.to_string(), val);
3052                Ok(Value::Dict(new_map))
3053            }
3054            "remove" => {
3055                let key = args.first().and_then(|a| a.as_str()).unwrap_or("");
3056                let mut new_map = map.clone();
3057                new_map.shift_remove(key);
3058                Ok(Value::Dict(new_map))
3059            }
3060            "merge" => {
3061                if let Some(Value::Dict(other)) = args.first() {
3062                    let mut new_map = map.clone();
3063                    for (k, v) in other {
3064                        new_map.insert(k.clone(), v.clone());
3065                    }
3066                    Ok(Value::Dict(new_map))
3067                } else {
3068                    Err(IonError::type_err(
3069                        ion_str!("merge requires a dict argument"),
3070                        line,
3071                        col,
3072                    ))
3073                }
3074            }
3075            "update" => {
3076                if let Some(Value::Dict(other)) = args.first() {
3077                    let mut new_map = map.clone();
3078                    for (k, v) in other {
3079                        new_map.insert(k.clone(), v.clone());
3080                    }
3081                    Ok(Value::Dict(new_map))
3082                } else {
3083                    Err(IonError::type_err(
3084                        ion_str!("update requires a dict argument"),
3085                        line,
3086                        col,
3087                    ))
3088                }
3089            }
3090            "keys_of" => {
3091                let target = args.first().ok_or_else(|| {
3092                    IonError::type_err(ion_str!("keys_of requires an argument"), line, col)
3093                })?;
3094                let keys: Vec<Value> = map
3095                    .iter()
3096                    .filter(|(_, v)| *v == target)
3097                    .map(|(k, _)| Value::Str(k.clone()))
3098                    .collect();
3099                Ok(Value::List(keys))
3100            }
3101            "zip" => {
3102                if let Some(Value::Dict(other)) = args.first() {
3103                    let mut result = indexmap::IndexMap::new();
3104                    for (k, v) in map {
3105                        if let Some(ov) = other.get(k) {
3106                            result.insert(k.clone(), Value::Tuple(vec![v.clone(), ov.clone()]));
3107                        }
3108                    }
3109                    Ok(Value::Dict(result))
3110                } else {
3111                    Err(IonError::type_err(
3112                        ion_str!("zip requires a dict argument"),
3113                        line,
3114                        col,
3115                    ))
3116                }
3117            }
3118            _ => Err(IonError::type_err(
3119                format!(
3120                    "{}{}{}",
3121                    ion_str!("dict has no method '"),
3122                    method,
3123                    ion_str!("'")
3124                ),
3125                line,
3126                col,
3127            )),
3128        }
3129    }
3130
3131    fn option_method(
3132        &self,
3133        val: &Value,
3134        method: &str,
3135        args: &[Value],
3136        line: usize,
3137        col: usize,
3138    ) -> Result<Value, IonError> {
3139        let opt = match val {
3140            Value::Option(o) => o,
3141            _ => return Err(IonError::type_err(ion_str!("expected Option"), line, col)),
3142        };
3143        match method {
3144            "is_some" => Ok(Value::Bool(opt.is_some())),
3145            "is_none" => Ok(Value::Bool(opt.is_none())),
3146            "unwrap" => match opt {
3147                Some(v) => Ok(*v.clone()),
3148                None => Err(IonError::runtime(
3149                    ion_str!("called unwrap on None"),
3150                    line,
3151                    col,
3152                )),
3153            },
3154            "unwrap_or" => Ok(opt
3155                .as_ref()
3156                .map(|v| *v.clone())
3157                .unwrap_or_else(|| args.first().cloned().unwrap_or(Value::Unit))),
3158            "expect" => match opt {
3159                Some(v) => Ok(*v.clone()),
3160                None => {
3161                    let msg = args
3162                        .first()
3163                        .and_then(|a| a.as_str())
3164                        .unwrap_or("called expect on None");
3165                    Err(IonError::runtime(msg.to_string(), line, col))
3166                }
3167            },
3168            _ => Err(IonError::type_err(
3169                format!(
3170                    "{}{}{}",
3171                    ion_str!("Option has no method '"),
3172                    method,
3173                    ion_str!("'")
3174                ),
3175                line,
3176                col,
3177            )),
3178        }
3179    }
3180
3181    fn result_method(
3182        &self,
3183        val: &Value,
3184        method: &str,
3185        args: &[Value],
3186        line: usize,
3187        col: usize,
3188    ) -> Result<Value, IonError> {
3189        let res = match val {
3190            Value::Result(r) => r,
3191            _ => return Err(IonError::type_err(ion_str!("expected Result"), line, col)),
3192        };
3193        match method {
3194            "is_ok" => Ok(Value::Bool(res.is_ok())),
3195            "is_err" => Ok(Value::Bool(res.is_err())),
3196            "unwrap" => match res {
3197                Ok(v) => Ok(*v.clone()),
3198                Err(e) => Err(IonError::runtime(
3199                    format!("{}{}", ion_str!("called unwrap on Err: "), e),
3200                    line,
3201                    col,
3202                )),
3203            },
3204            "unwrap_or" => Ok(match res {
3205                Ok(v) => *v.clone(),
3206                Err(_) => args.first().cloned().unwrap_or(Value::Unit),
3207            }),
3208            "expect" => match res {
3209                Ok(v) => Ok(*v.clone()),
3210                Err(e) => {
3211                    let msg = args
3212                        .first()
3213                        .and_then(|a| a.as_str())
3214                        .unwrap_or("called expect on Err");
3215                    Err(IonError::runtime(format!("{}: {}", msg, e), line, col))
3216                }
3217            },
3218            _ => Err(IonError::type_err(
3219                format!(
3220                    "{}{}{}",
3221                    ion_str!("Result has no method '"),
3222                    method,
3223                    ion_str!("'")
3224                ),
3225                line,
3226                col,
3227            )),
3228        }
3229    }
3230
3231    // ---- Function calls ----
3232
3233    /// Invoke a function value with arguments directly (not from the stack).
3234    fn invoke_value(
3235        &mut self,
3236        func: &Value,
3237        args: &[Value],
3238        line: usize,
3239        col: usize,
3240    ) -> Result<Value, IonError> {
3241        // Push func and args onto stack, then call
3242        self.stack.push(func.clone());
3243        for arg in args {
3244            self.stack.push(arg.clone());
3245        }
3246        self.call_function(args.len(), line, col)?;
3247        self.pop(line, col)
3248    }
3249
3250    fn eval_default_arg(
3251        &self,
3252        param_name: &str,
3253        default: &crate::ast::Expr,
3254        line: usize,
3255        col: usize,
3256    ) -> Result<Value, IonError> {
3257        let mut interp = crate::interpreter::Interpreter::with_env(self.env.clone());
3258        interp.types = self.types.clone();
3259        interp.eval_single_expr(default).map_err(|e| {
3260            IonError::runtime(
3261                format!(
3262                    "{}'{}': {}",
3263                    ion_str!("error evaluating default for "),
3264                    param_name,
3265                    e.message
3266                ),
3267                line,
3268                col,
3269            )
3270        })
3271    }
3272
3273    fn prepare_positional_function_args(
3274        &mut self,
3275        ion_fn: &crate::value::IonFn,
3276        args: &[Value],
3277        line: usize,
3278        col: usize,
3279    ) -> Result<Vec<Value>, IonError> {
3280        let mut prepared = Vec::with_capacity(ion_fn.params.len());
3281        for (i, param) in ion_fn.params.iter().enumerate() {
3282            let val = if i < args.len() {
3283                args[i].clone()
3284            } else if let Some(default) = &param.default {
3285                self.eval_default_arg(&param.name, default, line, col)?
3286            } else {
3287                return Err(IonError::runtime(
3288                    format!(
3289                        "{}{}{}{}{}{}",
3290                        ion_str!("function '"),
3291                        ion_fn.name,
3292                        ion_str!("' expected "),
3293                        ion_fn.params.len(),
3294                        ion_str!(" arguments, got "),
3295                        args.len(),
3296                    ),
3297                    line,
3298                    col,
3299                ));
3300            };
3301            self.env.define(param.name.clone(), val.clone(), false);
3302            prepared.push(val);
3303        }
3304        Ok(prepared)
3305    }
3306
3307    fn prepare_named_function_args(
3308        &mut self,
3309        ion_fn: &crate::value::IonFn,
3310        ordered: &[Option<Value>],
3311        line: usize,
3312        col: usize,
3313    ) -> Result<Vec<Value>, IonError> {
3314        let mut prepared = Vec::with_capacity(ion_fn.params.len());
3315        for (i, param) in ion_fn.params.iter().enumerate() {
3316            let val = if let Some(Some(val)) = ordered.get(i) {
3317                val.clone()
3318            } else if let Some(default) = &param.default {
3319                self.eval_default_arg(&param.name, default, line, col)?
3320            } else {
3321                return Err(IonError::runtime(
3322                    format!(
3323                        "{}{}{}",
3324                        ion_str!("missing argument '"),
3325                        param.name,
3326                        ion_str!("'"),
3327                    ),
3328                    line,
3329                    col,
3330                ));
3331            };
3332            self.env.define(param.name.clone(), val.clone(), false);
3333            prepared.push(val);
3334        }
3335        Ok(prepared)
3336    }
3337
3338    fn call_function(&mut self, arg_count: usize, line: usize, col: usize) -> Result<(), IonError> {
3339        // Stack: [..., func, arg0, arg1, ..., argN-1]
3340        let args_start = self.stack.len() - arg_count;
3341        let func_idx = args_start - 1;
3342        let mut func = self.stack[func_idx].clone();
3343        let mut args: Vec<Value> = self.stack[args_start..].to_vec();
3344        self.stack.truncate(func_idx);
3345
3346        // Trampoline loop: handles tail calls without growing the Rust stack
3347        loop {
3348            match func {
3349                #[cfg(all(feature = "legacy-threaded-concurrency", not(feature = "async-runtime")))]
3350                Value::BuiltinFn(ref name, _) if name == "timeout" => {
3351                    let result = self.builtin_timeout(&args, line, col)?;
3352                    self.stack.push(result);
3353                    return Ok(());
3354                }
3355                Value::BuiltinFn(_name, f) => {
3356                    let result = f(&args).map_err(|e| IonError::runtime(e, line, col))?;
3357                    self.stack.push(result);
3358                    return Ok(());
3359                }
3360                Value::BuiltinClosure(_name, f) => {
3361                    let result = f.call(&args).map_err(|e| IonError::runtime(e, line, col))?;
3362                    self.stack.push(result);
3363                    return Ok(());
3364                }
3365                #[cfg(feature = "async-runtime")]
3366                Value::AsyncBuiltinClosure(_, _) => {
3367                    return Err(IonError::runtime(
3368                        ion_str!(
3369                            "async host function cannot be called by the synchronous evaluator; use eval_async"
3370                        ),
3371                        line,
3372                        col,
3373                    ));
3374                }
3375                Value::Fn(ion_fn) => {
3376                    self.env.push_scope();
3377
3378                    for (name, val) in &ion_fn.captures {
3379                        self.env.define(name.clone(), val.clone(), false);
3380                    }
3381
3382                    // Save locals state for this function call
3383                    let saved_locals_base = self.locals_base;
3384                    let saved_locals_len = self.locals.len();
3385                    let saved_frames_len = self.local_frames.len();
3386                    let prepared_args =
3387                        match self.prepare_positional_function_args(&ion_fn, &args, line, col) {
3388                            Ok(args) => args,
3389                            Err(e) => {
3390                                self.env.pop_scope();
3391                                return Err(e);
3392                            }
3393                        };
3394
3395                    self.locals_base = self.locals.len(); // new base for function's locals
3396
3397                    // Push params as slot-based locals (slots 0..N relative to base)
3398                    for val in prepared_args {
3399                        self.locals.push(LocalSlot {
3400                            value: val,
3401                            mutable: false,
3402                        });
3403                    }
3404
3405                    let fn_id = ion_fn.fn_id;
3406                    let chunk_opt = if let Some(chunk) = self.fn_cache.get(&fn_id) {
3407                        Some(chunk.clone())
3408                    } else {
3409                        let compiler = crate::compiler::Compiler::new();
3410                        compiler
3411                            .compile_fn_body(&ion_fn.params, &ion_fn.body, line)
3412                            .ok()
3413                    };
3414                    if let Some(chunk) = chunk_opt {
3415                        self.fn_cache.entry(fn_id).or_insert_with(|| chunk.clone());
3416                        let saved_ip = self.ip;
3417                        let saved_iters = std::mem::take(&mut self.iterators);
3418                        self.ip = 0;
3419                        let result = self.run_chunk(&chunk);
3420                        self.ip = saved_ip;
3421                        self.iterators = saved_iters;
3422                        // Restore locals
3423                        self.locals.truncate(saved_locals_len);
3424                        self.local_frames.truncate(saved_frames_len);
3425                        self.locals_base = saved_locals_base;
3426                        self.env.pop_scope();
3427
3428                        // Check for pending tail call (trampoline)
3429                        if let Some((tail_func, tail_args)) = self.pending_tail_call.take() {
3430                            func = tail_func;
3431                            args = tail_args;
3432                            continue; // loop back without growing Rust stack
3433                        }
3434
3435                        match result {
3436                            Ok(val) => self.stack.push(val),
3437                            Err(e) if e.kind == crate::error::ErrorKind::PropagatedErr => {
3438                                self.stack.push(Value::Result(Err(Box::new(Value::Str(
3439                                    e.message.clone(),
3440                                )))));
3441                            }
3442                            Err(e) if e.kind == crate::error::ErrorKind::PropagatedNone => {
3443                                self.stack.push(Value::Option(None));
3444                            }
3445                            Err(e) => return Err(e),
3446                        }
3447                    } else {
3448                        // Restore locals before tree-walk fallback
3449                        self.locals.truncate(saved_locals_len);
3450                        self.local_frames.truncate(saved_frames_len);
3451                        self.locals_base = saved_locals_base;
3452                        let mut interp =
3453                            crate::interpreter::Interpreter::with_env(self.env.clone());
3454                        let result = interp.eval_block(&ion_fn.body);
3455                        self.env = interp.take_env();
3456                        self.env.pop_scope();
3457                        match result {
3458                            Ok(val) => self.stack.push(val),
3459                            Err(e) if e.kind == crate::error::ErrorKind::PropagatedErr => {
3460                                self.stack.push(Value::Result(Err(Box::new(Value::Str(
3461                                    e.message.clone(),
3462                                )))));
3463                            }
3464                            Err(e) if e.kind == crate::error::ErrorKind::PropagatedNone => {
3465                                self.stack.push(Value::Option(None));
3466                            }
3467                            Err(e) => return Err(e),
3468                        }
3469                    }
3470                    return Ok(());
3471                }
3472                _ => {
3473                    return Err(IonError::type_err(
3474                        format!("{}{}", ion_str!("cannot call "), func.type_name()),
3475                        line,
3476                        col,
3477                    ));
3478                }
3479            }
3480        }
3481    }
3482
3483    fn call_function_named(
3484        &mut self,
3485        arg_count: usize,
3486        named_map: &[(usize, String)],
3487        line: usize,
3488        col: usize,
3489    ) -> Result<(), IonError> {
3490        // Stack: [..., func, arg0, arg1, ..., argN-1]
3491        let args_start = self.stack.len() - arg_count;
3492        let func_idx = args_start - 1;
3493        let func = self.stack[func_idx].clone();
3494        let raw_args: Vec<Value> = self.stack[args_start..].to_vec();
3495        self.stack.truncate(func_idx);
3496
3497        match &func {
3498            Value::Fn(ion_fn) => {
3499                // Reorder args based on named_map
3500                let mut ordered = vec![None; ion_fn.params.len()];
3501                let mut pos_idx = 0;
3502                for (i, val) in raw_args.into_iter().enumerate() {
3503                    if let Some((_, ref name)) = named_map.iter().find(|(pos, _)| *pos == i) {
3504                        // Named arg: find param by name
3505                        let param_idx = ion_fn
3506                            .params
3507                            .iter()
3508                            .position(|p| &p.name == name)
3509                            .ok_or_else(|| {
3510                                IonError::runtime(
3511                                    format!(
3512                                        "{}'{}'{}'{}'",
3513                                        ion_str!("unknown parameter '"),
3514                                        name,
3515                                        ion_str!("' for function '"),
3516                                        ion_fn.name
3517                                    ),
3518                                    line,
3519                                    col,
3520                                )
3521                            })?;
3522                        ordered[param_idx] = Some(val);
3523                    } else {
3524                        // Positional arg: fill next available slot
3525                        while pos_idx < ordered.len() && ordered[pos_idx].is_some() {
3526                            pos_idx += 1;
3527                        }
3528                        if pos_idx < ordered.len() {
3529                            ordered[pos_idx] = Some(val);
3530                            pos_idx += 1;
3531                        }
3532                    }
3533                }
3534                // Fill defaults and push reordered args
3535                self.env.push_scope();
3536                for (name, val) in &ion_fn.captures {
3537                    self.env.define(name.clone(), val.clone(), false);
3538                }
3539                let reordered = match self.prepare_named_function_args(ion_fn, &ordered, line, col)
3540                {
3541                    Ok(args) => args,
3542                    Err(e) => {
3543                        self.env.pop_scope();
3544                        return Err(e);
3545                    }
3546                };
3547                self.env.pop_scope();
3548                // Push func + reordered args, then call normally
3549                self.stack.push(func.clone());
3550                for arg in &reordered {
3551                    self.stack.push(arg.clone());
3552                }
3553                self.call_function(reordered.len(), line, col)
3554            }
3555            Value::BuiltinFn(_, f) => {
3556                // Builtins don't support named args, just pass positionally
3557                let result = f(&raw_args).map_err(|e| IonError::runtime(e, line, col))?;
3558                self.stack.push(result);
3559                Ok(())
3560            }
3561            Value::BuiltinClosure(_, f) => {
3562                let result = f
3563                    .call(&raw_args)
3564                    .map_err(|e| IonError::runtime(e, line, col))?;
3565                self.stack.push(result);
3566                Ok(())
3567            }
3568            #[cfg(feature = "async-runtime")]
3569            Value::AsyncBuiltinClosure(_, _) => Err(IonError::runtime(
3570                ion_str!(
3571                    "async host function cannot be called by the synchronous evaluator; use eval_async"
3572                ),
3573                line,
3574                col,
3575            )),
3576            _ => Err(IonError::type_err(
3577                format!("cannot call {}", func.type_name()),
3578                line,
3579                col,
3580            )),
3581        }
3582    }
3583
3584    #[cfg(all(feature = "legacy-threaded-concurrency", not(feature = "async-runtime")))]
3585    fn builtin_timeout(&self, args: &[Value], line: usize, col: usize) -> Result<Value, IonError> {
3586        if args.len() < 2 {
3587            return Err(IonError::runtime(
3588                ion_str!("timeout(ms, fn) requires 2 arguments"),
3589                line,
3590                col,
3591            ));
3592        }
3593        let ms = args[0].as_int().ok_or_else(|| {
3594            IonError::runtime(
3595                ion_str!("timeout: first argument must be int (ms)"),
3596                line,
3597                col,
3598            )
3599        })?;
3600        let func = args[1].clone();
3601        let captured_env = self.env.capture();
3602        let cancel_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
3603        let task = crate::async_rt::spawn_task_with_cancel(cancel_flag, move |flag| {
3604            let mut child = crate::interpreter::Interpreter::new();
3605            child.cancel_flag = Some(flag);
3606            for (name, val) in captured_env {
3607                child.env.define(name, val, false);
3608            }
3609            // Build a program that calls the function
3610            let program = crate::ast::Program {
3611                stmts: vec![crate::ast::Stmt {
3612                    kind: crate::ast::StmtKind::ExprStmt {
3613                        expr: crate::ast::Expr {
3614                            kind: crate::ast::ExprKind::Call {
3615                                func: Box::new(crate::ast::Expr {
3616                                    kind: crate::ast::ExprKind::Ident("__timeout_fn__".to_string()),
3617                                    span: crate::ast::Span { line: 0, col: 0 },
3618                                }),
3619                                args: vec![],
3620                            },
3621                            span: crate::ast::Span { line: 0, col: 0 },
3622                        },
3623                        has_semi: false,
3624                    },
3625                    span: crate::ast::Span { line: 0, col: 0 },
3626                }],
3627            };
3628            child.env.define("__timeout_fn__".to_string(), func, false);
3629            child.eval_program(&program)
3630        });
3631        match task.join_timeout(std::time::Duration::from_millis(ms as u64)) {
3632            Some(Ok(val)) => Ok(Value::Option(Some(Box::new(val)))),
3633            Some(Err(e)) => Err(e),
3634            None => {
3635                task.cancel();
3636                Ok(Value::Option(None))
3637            }
3638        }
3639    }
3640}