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