Skip to main content

ion_core/
vm.rs

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