surreal/
scheduler.rs

1//! The scheduler / VM execution engine.
2
3use std::collections::{HashMap, VecDeque};
4
5use num_bigint::BigInt;
6
7use crate::{
8    CallFrame, Instruction, Message, Module, Operand, Pattern, Pid, Process, ProcessStatus,
9    Register, Source, SystemMsg, TryFrame, Value,
10};
11
12/// Result of stepping the scheduler
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum StepResult {
15    /// There's more work to do
16    Busy,
17    /// All processes are done or waiting
18    Idle,
19}
20
21/// Result of executing a single instruction
22#[derive(Debug)]
23enum ExecResult {
24    /// Continue execution, used N reductions
25    Continue(u32),
26    /// Yield back to scheduler (e.g., spawned a process)
27    #[allow(dead_code)]
28    Yield(u32),
29    /// Jump to a specific instruction (sets PC directly, no auto-increment)
30    Jump(usize, u32),
31    /// Waiting for a message
32    Wait,
33    /// Process finished normally (exit reason: :normal)
34    Done,
35    /// Process crashed (exit reason: :crashed)
36    Crash,
37    /// Process exited with a specific reason
38    Exit(Value),
39    /// Exception thrown (class, reason)
40    Throw(Value, Value),
41}
42
43/// Platform-specific logging
44fn log(msg: &str) {
45    #[cfg(target_arch = "wasm32")]
46    web_sys::console::log_1(&msg.into());
47
48    #[cfg(not(target_arch = "wasm32"))]
49    println!("{}", msg);
50}
51
52/// A pending timer
53#[derive(Debug, Clone)]
54pub struct Timer {
55    /// Unique timer reference
56    pub timer_ref: u64,
57    /// Process that will receive the message
58    pub target: Pid,
59    /// Message to send when timer fires
60    pub message: Value,
61    /// Remaining reductions until timer fires
62    pub remaining: u32,
63}
64
65/// The scheduler / VM state
66#[derive(Debug)]
67pub struct Scheduler {
68    pub processes: HashMap<Pid, Process>,
69    pub ready_queue: VecDeque<Pid>,
70    pub next_pid: u64,
71    /// Process registry: name -> pid
72    pub registry: HashMap<String, Pid>,
73    /// Module registry: name -> module
74    pub modules: HashMap<String, Module>,
75    /// Output buffer from print instructions
76    pub output: Vec<String>,
77    /// Global reference counter for make_ref and timer_ref
78    pub next_ref: u64,
79    /// Pending timers
80    pub timers: Vec<Timer>,
81}
82
83impl Scheduler {
84    pub fn new() -> Self {
85        Self {
86            processes: HashMap::new(),
87            ready_queue: VecDeque::new(),
88            next_pid: 0,
89            registry: HashMap::new(),
90            modules: HashMap::new(),
91            output: Vec::new(),
92            next_ref: 0,
93            timers: Vec::new(),
94        }
95    }
96
97    /// Load a module into the VM.
98    pub fn load_module(&mut self, module: Module) -> Result<(), String> {
99        if self.modules.contains_key(&module.name) {
100            return Err(format!("Module {} already loaded", module.name));
101        }
102        self.modules.insert(module.name.clone(), module);
103        Ok(())
104    }
105
106    /// Get an instruction from a process (checking module or inline code).
107    fn get_instruction(&self, process: &Process) -> Option<Instruction> {
108        if let Some(ref module_name) = process.current_module {
109            // Running module code
110            let module = self.modules.get(module_name)?;
111            module.code.get(process.pc).cloned()
112        } else if let Some(ref code) = process.inline_code {
113            // Running inline code
114            code.get(process.pc).cloned()
115        } else {
116            None
117        }
118    }
119
120    /// Get the code length for a process.
121    fn get_code_len(&self, process: &Process) -> usize {
122        if let Some(ref module_name) = process.current_module {
123            self.modules
124                .get(module_name)
125                .map(|m| m.code.len())
126                .unwrap_or(0)
127        } else if let Some(ref code) = process.inline_code {
128            code.len()
129        } else {
130            0
131        }
132    }
133
134    /// Take and clear the output buffer
135    pub fn take_output(&mut self) -> Vec<String> {
136        std::mem::take(&mut self.output)
137    }
138
139    /// Spawn a root process (no parent)
140    pub fn spawn(&mut self, code: Vec<Instruction>) -> Pid {
141        self.spawn_with_parent(code, None)
142    }
143
144    /// Spawn a process with an optional parent
145    pub fn spawn_with_parent(&mut self, code: Vec<Instruction>, parent: Option<Pid>) -> Pid {
146        let pid = Pid(self.next_pid);
147        self.next_pid += 1;
148
149        let process = Process::new(pid, parent, code);
150        self.processes.insert(pid, process);
151        self.ready_queue.push_back(pid);
152
153        pid
154    }
155
156    /// Run up to `budget` reductions, returns whether there's more work
157    pub fn step(&mut self, budget: u32) -> StepResult {
158        let mut remaining = budget;
159
160        // Tick timeouts for waiting processes
161        self.tick_timeouts();
162
163        // Tick timers and fire expired ones
164        self.tick_timers(budget);
165
166        while remaining > 0 {
167            // Get next ready process
168            let Some(pid) = self.ready_queue.pop_front() else {
169                // No ready processes - check if any are waiting with timeouts or timers
170                if self.has_pending_timeouts() || !self.timers.is_empty() {
171                    return StepResult::Busy; // Keep ticking
172                }
173                return StepResult::Idle;
174            };
175
176            // Run it for up to `remaining` reductions
177            let used = self.run_process(pid, remaining);
178            remaining = remaining.saturating_sub(used);
179        }
180
181        if self.ready_queue.is_empty() && !self.has_pending_timeouts() && self.timers.is_empty() {
182            StepResult::Idle
183        } else {
184            StepResult::Busy
185        }
186    }
187
188    /// Check if any waiting process has a pending timeout
189    fn has_pending_timeouts(&self) -> bool {
190        self.processes
191            .values()
192            .any(|p| p.status == ProcessStatus::Waiting && p.timeout.is_some())
193    }
194
195    /// Decrement timeouts for waiting processes, wake those that expired
196    fn tick_timeouts(&mut self) {
197        let mut to_wake = Vec::new();
198
199        for (pid, process) in &mut self.processes {
200            if process.status == ProcessStatus::Waiting {
201                if let Some(ref mut t) = process.timeout {
202                    if *t > 0 {
203                        *t -= 1;
204                    }
205                    if *t == 0 {
206                        process.status = ProcessStatus::Ready;
207                        to_wake.push(*pid);
208                    }
209                }
210            }
211        }
212
213        for pid in to_wake {
214            self.ready_queue.push_back(pid);
215        }
216    }
217
218    /// Tick timers and fire any that have expired
219    fn tick_timers(&mut self, elapsed: u32) {
220        let mut fired = Vec::new();
221        let mut i = 0;
222
223        while i < self.timers.len() {
224            let timer = &mut self.timers[i];
225            timer.remaining = timer.remaining.saturating_sub(elapsed);
226
227            if timer.remaining == 0 {
228                // Timer fired - collect it for processing
229                fired.push(self.timers.remove(i));
230            } else {
231                i += 1;
232            }
233        }
234
235        // Deliver messages for fired timers
236        for timer in fired {
237            if let Some(process) = self.processes.get_mut(&timer.target) {
238                // Send the message
239                let msg_str = format!("{:?}", timer.message);
240                process.mailbox.push_back(Message::User(msg_str));
241
242                // Wake up if waiting
243                if process.status == ProcessStatus::Waiting {
244                    process.status = ProcessStatus::Ready;
245                    self.ready_queue.push_back(timer.target);
246                }
247            }
248        }
249    }
250
251    /// Run a single process for up to `budget` reductions
252    /// Returns how many reductions were used
253    fn run_process(&mut self, pid: Pid, budget: u32) -> u32 {
254        let mut used = 0;
255
256        while used < budget {
257            let Some(process) = self.processes.get(&pid) else {
258                break;
259            };
260
261            let code_len = self.get_code_len(process);
262            if process.pc >= code_len {
263                // Process finished
264                self.finish_process_with_reason(
265                    pid,
266                    ProcessStatus::Done,
267                    Value::Atom("normal".to_string()),
268                );
269                break;
270            }
271
272            // Get instruction (from module or inline code)
273            let Some(instruction) = self.get_instruction(process) else {
274                self.finish_process_with_reason(
275                    pid,
276                    ProcessStatus::Crashed,
277                    Value::Atom("crashed".to_string()),
278                );
279                break;
280            };
281
282            match self.execute(pid, instruction) {
283                ExecResult::Continue(cost) => {
284                    used += cost;
285                    if let Some(p) = self.processes.get_mut(&pid) {
286                        p.pc += 1;
287                    }
288                }
289                ExecResult::Yield(cost) => {
290                    used += cost;
291                    if let Some(p) = self.processes.get_mut(&pid) {
292                        p.pc += 1;
293                    }
294                    // Re-queue and return
295                    self.ready_queue.push_back(pid);
296                    break;
297                }
298                ExecResult::Jump(target, cost) => {
299                    used += cost;
300                    if let Some(p) = self.processes.get_mut(&pid) {
301                        p.pc = target;
302                    }
303                }
304                ExecResult::Wait => {
305                    // Don't advance PC, don't requeue (waiting for message)
306                    if let Some(p) = self.processes.get_mut(&pid) {
307                        p.status = ProcessStatus::Waiting;
308                    }
309                    break;
310                }
311                ExecResult::Done => {
312                    // Normal exit with :normal reason
313                    self.finish_process_with_reason(
314                        pid,
315                        ProcessStatus::Done,
316                        Value::Atom("normal".to_string()),
317                    );
318                    break;
319                }
320                ExecResult::Crash => {
321                    // Abnormal exit with :crashed reason
322                    self.finish_process_with_reason(
323                        pid,
324                        ProcessStatus::Crashed,
325                        Value::Atom("crashed".to_string()),
326                    );
327                    break;
328                }
329                ExecResult::Exit(reason) => {
330                    // Exit with custom reason
331                    let status = if reason == Value::Atom("normal".to_string()) {
332                        ProcessStatus::Done
333                    } else {
334                        ProcessStatus::Crashed
335                    };
336                    self.finish_process_with_reason(pid, status, reason);
337                    break;
338                }
339                ExecResult::Throw(class, reason) => {
340                    // Handle exception - unwind to nearest catch handler
341                    let Some(process) = self.processes.get_mut(&pid) else {
342                        break;
343                    };
344
345                    if let Some(frame) = process.try_stack.pop() {
346                        // Build stacktrace (simplified - just current PC)
347                        let stacktrace = vec![format!("pc:{}", process.pc)];
348
349                        // Store exception for GetException
350                        process.current_exception = Some((class, reason, stacktrace));
351
352                        // Unwind stacks to saved depths
353                        process.call_stack.truncate(frame.call_stack_depth);
354                        process.stack.truncate(frame.stack_depth);
355
356                        // Jump to catch handler
357                        process.pc = frame.catch_target;
358                        used += 1;
359                        // Continue execution (don't break)
360                    } else {
361                        // No handler - crash with exception
362                        let exit_reason = Value::Tuple(vec![class, reason]);
363                        self.finish_process_with_reason(pid, ProcessStatus::Crashed, exit_reason);
364                        break;
365                    }
366                }
367            }
368        }
369
370        // If we used full budget but process isn't done, requeue it
371        if used >= budget {
372            if let Some(p) = self.processes.get(&pid) {
373                let code_len = self.get_code_len(p);
374                if p.status == ProcessStatus::Ready && p.pc < code_len {
375                    self.ready_queue.push_back(pid);
376                }
377            }
378        }
379
380        used
381    }
382
383    fn execute(&mut self, pid: Pid, instruction: Instruction) -> ExecResult {
384        match instruction {
385            Instruction::End => ExecResult::Done,
386
387            Instruction::Work { amount } => ExecResult::Continue(amount),
388
389            Instruction::Spawn { code, dest } => {
390                let child_pid = self.spawn_with_parent(code, Some(pid));
391                if let Some(p) = self.processes.get_mut(&pid) {
392                    p.registers[dest.0 as usize] = Value::Pid(child_pid);
393                }
394                ExecResult::Continue(1)
395            }
396
397            Instruction::SpawnLink { code, dest } => {
398                let child_pid = self.spawn_with_parent(code, Some(pid));
399
400                // Establish bidirectional link atomically
401                if let Some(parent) = self.processes.get_mut(&pid) {
402                    parent.registers[dest.0 as usize] = Value::Pid(child_pid);
403                    if !parent.links.contains(&child_pid) {
404                        parent.links.push(child_pid);
405                    }
406                }
407                if let Some(child) = self.processes.get_mut(&child_pid) {
408                    if !child.links.contains(&pid) {
409                        child.links.push(pid);
410                    }
411                }
412
413                ExecResult::Continue(1)
414            }
415
416            Instruction::Send { to, msg } => {
417                let target_pid = self.resolve_pid(pid, &to);
418                if let Some(target_pid) = target_pid {
419                    if let Some(target) = self.processes.get_mut(&target_pid) {
420                        target.mailbox.push_back(Message::User(msg));
421                        // Wake up if waiting
422                        if target.status == ProcessStatus::Waiting {
423                            target.status = ProcessStatus::Ready;
424                            self.ready_queue.push_back(target_pid);
425                        }
426                    }
427                }
428                ExecResult::Continue(1)
429            }
430
431            Instruction::Receive { dest } => {
432                let Some(process) = self.processes.get_mut(&pid) else {
433                    return ExecResult::Crash;
434                };
435
436                // Take the first message (user or system)
437                if let Some(msg) = process.mailbox.pop_front() {
438                    let value = Self::message_to_value(msg);
439                    process.registers[dest.0 as usize] = value;
440                    process.timeout = None;
441                    ExecResult::Continue(1)
442                } else {
443                    ExecResult::Wait
444                }
445            }
446
447            Instruction::ReceiveTimeout { dest, timeout } => {
448                let Some(process) = self.processes.get_mut(&pid) else {
449                    return ExecResult::Crash;
450                };
451
452                // Take the first message if available
453                if let Some(msg) = process.mailbox.pop_front() {
454                    let value = Self::message_to_value(msg);
455                    process.registers[dest.0 as usize] = value;
456                    process.timeout = None;
457                    ExecResult::Continue(1)
458                } else if process.timeout == Some(0) {
459                    // Timeout expired
460                    process.registers[dest.0 as usize] = Value::String("TIMEOUT".into());
461                    process.timeout = None;
462                    ExecResult::Continue(1)
463                } else {
464                    // Set or keep timeout, wait
465                    if process.timeout.is_none() {
466                        process.timeout = Some(timeout);
467                    }
468                    ExecResult::Wait
469                }
470            }
471
472            Instruction::Link { target } => {
473                let Some(target_pid) = self.resolve_pid(pid, &target) else {
474                    return ExecResult::Crash;
475                };
476
477                // Add bidirectional link
478                if let Some(p) = self.processes.get_mut(&pid) {
479                    if !p.links.contains(&target_pid) {
480                        p.links.push(target_pid);
481                    }
482                }
483                if let Some(t) = self.processes.get_mut(&target_pid) {
484                    if !t.links.contains(&pid) {
485                        t.links.push(pid);
486                    }
487                }
488
489                ExecResult::Continue(1)
490            }
491
492            Instruction::Unlink { target } => {
493                let Some(target_pid) = self.resolve_pid(pid, &target) else {
494                    return ExecResult::Crash;
495                };
496
497                // Remove bidirectional link
498                if let Some(p) = self.processes.get_mut(&pid) {
499                    p.links.retain(|&linked| linked != target_pid);
500                }
501                if let Some(t) = self.processes.get_mut(&target_pid) {
502                    t.links.retain(|&linked| linked != pid);
503                }
504
505                ExecResult::Continue(1)
506            }
507
508            Instruction::Monitor { target, dest } => {
509                let Some(target_pid) = self.resolve_pid(pid, &target) else {
510                    return ExecResult::Crash;
511                };
512
513                // Generate a unique monitor reference
514                let monitor_ref = self.next_ref;
515                self.next_ref += 1;
516
517                // Add monitor on caller side
518                if let Some(p) = self.processes.get_mut(&pid) {
519                    p.monitors.push((monitor_ref, target_pid));
520                }
521
522                // Add to target's monitored_by list
523                if let Some(t) = self.processes.get_mut(&target_pid) {
524                    t.monitored_by.push((monitor_ref, pid));
525                }
526
527                // Store the ref in dest register
528                if let Some(p) = self.processes.get_mut(&pid) {
529                    p.registers[dest.0 as usize] = Value::Ref(monitor_ref);
530                }
531
532                ExecResult::Continue(1)
533            }
534
535            Instruction::Demonitor { monitor_ref } => {
536                let Some(process) = self.processes.get(&pid) else {
537                    return ExecResult::Crash;
538                };
539                let Value::Ref(ref_id) = process.registers[monitor_ref.0 as usize] else {
540                    return ExecResult::Crash;
541                };
542                let ref_id = ref_id;
543
544                // Find and remove the monitor from caller's list
545                let target_pid = if let Some(p) = self.processes.get_mut(&pid) {
546                    let pos = p.monitors.iter().position(|(r, _)| *r == ref_id);
547                    if let Some(idx) = pos {
548                        let (_, target) = p.monitors.remove(idx);
549                        Some(target)
550                    } else {
551                        None
552                    }
553                } else {
554                    None
555                };
556
557                // Remove from target's monitored_by list
558                if let Some(target_pid) = target_pid {
559                    if let Some(t) = self.processes.get_mut(&target_pid) {
560                        t.monitored_by.retain(|(r, _)| *r != ref_id);
561                    }
562                }
563
564                ExecResult::Continue(1)
565            }
566
567            Instruction::Register { name } => {
568                // Register current process with name
569                self.registry.insert(name, pid);
570                ExecResult::Continue(1)
571            }
572
573            Instruction::Unregister { name } => {
574                // Remove name from registry
575                self.registry.remove(&name);
576                ExecResult::Continue(1)
577            }
578
579            Instruction::WhereIs { name, dest } => {
580                let value = self
581                    .registry
582                    .get(&name)
583                    .map(|p| Value::Pid(*p))
584                    .unwrap_or(Value::None);
585
586                if let Some(p) = self.processes.get_mut(&pid) {
587                    p.registers[dest.0 as usize] = value;
588                }
589                ExecResult::Continue(1)
590            }
591
592            Instruction::Print { source } => {
593                if let Some(process) = self.processes.get(&pid) {
594                    let value = self.resolve_source(process, &source);
595                    let msg = format!("[Pid({})] {:?}", pid.0, value);
596                    log(&msg);
597                    self.output.push(msg);
598                }
599                ExecResult::Continue(1)
600            }
601
602            Instruction::Crash => ExecResult::Crash,
603
604            Instruction::Exit { reason } => {
605                if let Some(p) = self.processes.get(&pid) {
606                    let exit_reason = p.registers[reason.0 as usize].clone();
607                    ExecResult::Exit(exit_reason)
608                } else {
609                    ExecResult::Crash
610                }
611            }
612
613            Instruction::TrapExit { enable } => {
614                if let Some(p) = self.processes.get_mut(&pid) {
615                    p.trap_exit = enable;
616                }
617                ExecResult::Continue(1)
618            }
619
620            // ========== Arithmetic ==========
621            Instruction::LoadInt { value, dest } => {
622                if let Some(p) = self.processes.get_mut(&pid) {
623                    p.registers[dest.0 as usize] = Value::Int(value);
624                }
625                ExecResult::Continue(1)
626            }
627
628            Instruction::Move { source, dest } => {
629                if let Some(p) = self.processes.get_mut(&pid) {
630                    let value = p.registers[source.0 as usize].clone();
631                    p.registers[dest.0 as usize] = value;
632                }
633                ExecResult::Continue(1)
634            }
635
636            Instruction::Add { a, b, dest } => self.bigint_arith_op(
637                pid,
638                &a,
639                &b,
640                dest,
641                |x, y| x.checked_add(y).map(Value::Int),
642                |x, y| x + y,
643            ),
644
645            Instruction::Sub { a, b, dest } => self.bigint_arith_op(
646                pid,
647                &a,
648                &b,
649                dest,
650                |x, y| x.checked_sub(y).map(Value::Int),
651                |x, y| x - y,
652            ),
653
654            Instruction::Mul { a, b, dest } => self.bigint_arith_op(
655                pid,
656                &a,
657                &b,
658                dest,
659                |x, y| x.checked_mul(y).map(Value::Int),
660                |x, y| x * y,
661            ),
662
663            Instruction::Div { a, b, dest } => {
664                let Some(process) = self.processes.get(&pid) else {
665                    return ExecResult::Crash;
666                };
667                let av = self.resolve_operand_value(process, &a);
668                let bv = self.resolve_operand_value(process, &b);
669                match (av, bv) {
670                    (Some(x), Some(y)) => {
671                        if y.is_zero() {
672                            return ExecResult::Crash; // Division by zero
673                        }
674                        let result = match (x.to_bigint(), y.to_bigint()) {
675                            (Some(a), Some(b)) => Value::from_bigint(a / b),
676                            _ => return ExecResult::Crash,
677                        };
678                        if let Some(p) = self.processes.get_mut(&pid) {
679                            p.registers[dest.0 as usize] = result;
680                        }
681                        ExecResult::Continue(1)
682                    }
683                    _ => ExecResult::Crash,
684                }
685            }
686
687            Instruction::Mod { a, b, dest } => {
688                let Some(process) = self.processes.get(&pid) else {
689                    return ExecResult::Crash;
690                };
691                let av = self.resolve_operand_value(process, &a);
692                let bv = self.resolve_operand_value(process, &b);
693                match (av, bv) {
694                    (Some(x), Some(y)) => {
695                        if y.is_zero() {
696                            return ExecResult::Crash; // Division by zero
697                        }
698                        let result = match (x.to_bigint(), y.to_bigint()) {
699                            (Some(a), Some(b)) => Value::from_bigint(a % b),
700                            _ => return ExecResult::Crash,
701                        };
702                        if let Some(p) = self.processes.get_mut(&pid) {
703                            p.registers[dest.0 as usize] = result;
704                        }
705                        ExecResult::Continue(1)
706                    }
707                    _ => ExecResult::Crash,
708                }
709            }
710
711            // ========== Comparisons ==========
712            Instruction::Eq { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x == y),
713
714            Instruction::Ne { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x != y),
715
716            Instruction::Lt { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x < y),
717
718            Instruction::Lte { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x <= y),
719
720            Instruction::Gt { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x > y),
721
722            Instruction::Gte { a, b, dest } => self.bigint_cmp_op(pid, &a, &b, dest, |x, y| x >= y),
723
724            // ========== Control Flow ==========
725            Instruction::Jump { target } => ExecResult::Jump(target, 1),
726
727            Instruction::JumpIf { cond, target } => {
728                let Some(process) = self.processes.get(&pid) else {
729                    return ExecResult::Crash;
730                };
731                let val = self.resolve_operand(process, &cond);
732                match val {
733                    Some(n) if n != 0 => ExecResult::Jump(target, 1),
734                    _ => ExecResult::Continue(1), // Falsy, continue to next instruction
735                }
736            }
737
738            Instruction::JumpUnless { cond, target } => {
739                let Some(process) = self.processes.get(&pid) else {
740                    return ExecResult::Crash;
741                };
742                let val = self.resolve_operand(process, &cond);
743                match val {
744                    Some(0) | None => ExecResult::Jump(target, 1), // Falsy, jump
745                    _ => ExecResult::Continue(1),                  // Truthy, continue
746                }
747            }
748
749            Instruction::Call { target } => {
750                if let Some(p) = self.processes.get_mut(&pid) {
751                    // Push call frame with return context
752                    p.call_stack.push(CallFrame {
753                        module: p.current_module.clone(),
754                        return_pc: p.pc + 1,
755                    });
756                }
757                ExecResult::Jump(target, 1)
758            }
759
760            Instruction::Return => {
761                let Some(process) = self.processes.get_mut(&pid) else {
762                    return ExecResult::Crash;
763                };
764                if let Some(frame) = process.call_stack.pop() {
765                    // Restore module context
766                    process.current_module = frame.module;
767                    ExecResult::Jump(frame.return_pc, 1)
768                } else {
769                    // Empty call stack - end the process
770                    ExecResult::Done
771                }
772            }
773
774            Instruction::CallMFA {
775                module,
776                function,
777                arity,
778            } => {
779                // Look up the module
780                let Some(mod_ref) = self.modules.get(&module) else {
781                    log(&format!("CallMFA: module {} not found", module));
782                    return ExecResult::Crash;
783                };
784
785                // Look up the function
786                let Some(func) = mod_ref.get_function(&function, arity) else {
787                    log(&format!(
788                        "CallMFA: function {}:{}/{} not found",
789                        module, function, arity
790                    ));
791                    return ExecResult::Crash;
792                };
793
794                let entry = func.entry;
795
796                // Push call frame and switch module
797                if let Some(p) = self.processes.get_mut(&pid) {
798                    p.call_stack.push(CallFrame {
799                        module: p.current_module.clone(),
800                        return_pc: p.pc + 1,
801                    });
802                    p.current_module = Some(module.clone());
803                }
804                ExecResult::Jump(entry, 1)
805            }
806
807            Instruction::CallLocal { function, arity } => {
808                let Some(process) = self.processes.get(&pid) else {
809                    return ExecResult::Crash;
810                };
811
812                // Must be in a module
813                let Some(ref module_name) = process.current_module else {
814                    log("CallLocal: not in a module");
815                    return ExecResult::Crash;
816                };
817
818                // Look up the function in current module
819                let Some(mod_ref) = self.modules.get(module_name) else {
820                    return ExecResult::Crash;
821                };
822
823                let Some(func) = mod_ref.get_function(&function, arity) else {
824                    log(&format!(
825                        "CallLocal: function {}:{}/{} not found",
826                        module_name, function, arity
827                    ));
828                    return ExecResult::Crash;
829                };
830
831                let entry = func.entry;
832
833                // Push call frame (same module)
834                if let Some(p) = self.processes.get_mut(&pid) {
835                    p.call_stack.push(CallFrame {
836                        module: p.current_module.clone(),
837                        return_pc: p.pc + 1,
838                    });
839                }
840                ExecResult::Jump(entry, 1)
841            }
842
843            Instruction::TailCallMFA {
844                module,
845                function,
846                arity,
847            } => {
848                // Look up the module
849                let Some(mod_ref) = self.modules.get(&module) else {
850                    log(&format!("TailCallMFA: module {} not found", module));
851                    return ExecResult::Crash;
852                };
853
854                // Look up the function
855                let Some(func) = mod_ref.get_function(&function, arity) else {
856                    log(&format!(
857                        "TailCallMFA: function {}:{}/{} not found",
858                        module, function, arity
859                    ));
860                    return ExecResult::Crash;
861                };
862
863                let entry = func.entry;
864
865                // No call frame push - tail call, just switch module
866                if let Some(p) = self.processes.get_mut(&pid) {
867                    p.current_module = Some(module.clone());
868                }
869                ExecResult::Jump(entry, 1)
870            }
871
872            Instruction::TailCallLocal { function, arity } => {
873                let Some(process) = self.processes.get(&pid) else {
874                    return ExecResult::Crash;
875                };
876
877                // Must be in a module
878                let Some(ref module_name) = process.current_module else {
879                    log("TailCallLocal: not in a module");
880                    return ExecResult::Crash;
881                };
882
883                // Look up the function in current module
884                let Some(mod_ref) = self.modules.get(module_name) else {
885                    return ExecResult::Crash;
886                };
887
888                let Some(func) = mod_ref.get_function(&function, arity) else {
889                    log(&format!(
890                        "TailCallLocal: function {}:{}/{} not found",
891                        module_name, function, arity
892                    ));
893                    return ExecResult::Crash;
894                };
895
896                let entry = func.entry;
897
898                // No call frame push - tail call (stay in same module)
899                ExecResult::Jump(entry, 1)
900            }
901
902            Instruction::MakeFun {
903                module,
904                function,
905                arity,
906                dest,
907            } => {
908                // Verify function exists
909                let Some(mod_ref) = self.modules.get(&module) else {
910                    log(&format!("MakeFun: module {} not found", module));
911                    return ExecResult::Crash;
912                };
913
914                if mod_ref.get_function(&function, arity).is_none() {
915                    log(&format!(
916                        "MakeFun: function {}:{}/{} not found",
917                        module, function, arity
918                    ));
919                    return ExecResult::Crash;
920                }
921
922                if let Some(p) = self.processes.get_mut(&pid) {
923                    p.registers[dest.0 as usize] = Value::Fun {
924                        module: module.clone(),
925                        function: function.clone(),
926                        arity,
927                    };
928                }
929                ExecResult::Continue(1)
930            }
931
932            Instruction::MakeClosure {
933                module,
934                function,
935                arity,
936                captures,
937                dest,
938            } => {
939                // Verify function exists (arity for closure = explicit arity + captured count)
940                let total_arity = arity + captures.len() as u8;
941                let Some(mod_ref) = self.modules.get(&module) else {
942                    log(&format!("MakeClosure: module {} not found", module));
943                    return ExecResult::Crash;
944                };
945
946                if mod_ref.get_function(&function, total_arity).is_none() {
947                    log(&format!(
948                        "MakeClosure: function {}:{}/{} not found",
949                        module, function, total_arity
950                    ));
951                    return ExecResult::Crash;
952                }
953
954                // Capture values from registers
955                let Some(process) = self.processes.get(&pid) else {
956                    return ExecResult::Crash;
957                };
958
959                let captured: Vec<Value> = captures
960                    .iter()
961                    .map(|r| process.registers[r.0 as usize].clone())
962                    .collect();
963
964                if let Some(p) = self.processes.get_mut(&pid) {
965                    p.registers[dest.0 as usize] = Value::Closure {
966                        module: module.clone(),
967                        function: function.clone(),
968                        arity,
969                        captured,
970                    };
971                }
972                ExecResult::Continue(1)
973            }
974
975            Instruction::Apply { fun, arity } => {
976                let Some(process) = self.processes.get(&pid) else {
977                    return ExecResult::Crash;
978                };
979
980                // Handle both Fun and Closure
981                match &process.registers[fun.0 as usize] {
982                    Value::Fun {
983                        module,
984                        function,
985                        arity: fun_arity,
986                    } => {
987                        if *fun_arity != arity {
988                            log(&format!(
989                                "Apply: arity mismatch, expected {}, got {}",
990                                fun_arity, arity
991                            ));
992                            return ExecResult::Crash;
993                        }
994
995                        // Look up the function
996                        let Some(mod_ref) = self.modules.get(module) else {
997                            log(&format!("Apply: module {} not found", module));
998                            return ExecResult::Crash;
999                        };
1000
1001                        let Some(func) = mod_ref.get_function(function, arity) else {
1002                            log(&format!(
1003                                "Apply: function {}:{}/{} not found",
1004                                module, function, arity
1005                            ));
1006                            return ExecResult::Crash;
1007                        };
1008
1009                        let entry = func.entry;
1010                        let module = module.clone();
1011
1012                        // Push call frame and switch module
1013                        if let Some(p) = self.processes.get_mut(&pid) {
1014                            p.call_stack.push(CallFrame {
1015                                module: p.current_module.clone(),
1016                                return_pc: p.pc + 1,
1017                            });
1018                            p.current_module = Some(module);
1019                        }
1020                        ExecResult::Jump(entry, 1)
1021                    }
1022
1023                    Value::Closure {
1024                        module,
1025                        function,
1026                        arity: closure_arity,
1027                        captured,
1028                    } => {
1029                        if *closure_arity != arity {
1030                            log(&format!(
1031                                "Apply: closure arity mismatch, expected {}, got {}",
1032                                closure_arity, arity
1033                            ));
1034                            return ExecResult::Crash;
1035                        }
1036
1037                        // Total arity = explicit args + captured values
1038                        let total_arity = arity + captured.len() as u8;
1039
1040                        // Look up the function
1041                        let Some(mod_ref) = self.modules.get(module) else {
1042                            log(&format!("Apply: module {} not found", module));
1043                            return ExecResult::Crash;
1044                        };
1045
1046                        let Some(func) = mod_ref.get_function(function, total_arity) else {
1047                            log(&format!(
1048                                "Apply: function {}:{}/{} not found",
1049                                module, function, total_arity
1050                            ));
1051                            return ExecResult::Crash;
1052                        };
1053
1054                        let entry = func.entry;
1055                        let module = module.clone();
1056                        let captured = captured.clone();
1057
1058                        // Copy captured values to registers after explicit args
1059                        // and push call frame
1060                        if let Some(p) = self.processes.get_mut(&pid) {
1061                            for (i, val) in captured.into_iter().enumerate() {
1062                                p.registers[arity as usize + i] = val;
1063                            }
1064                            p.call_stack.push(CallFrame {
1065                                module: p.current_module.clone(),
1066                                return_pc: p.pc + 1,
1067                            });
1068                            p.current_module = Some(module);
1069                        }
1070                        ExecResult::Jump(entry, 1)
1071                    }
1072
1073                    _ => {
1074                        log("Apply: not a function or closure");
1075                        ExecResult::Crash
1076                    }
1077                }
1078            }
1079
1080            Instruction::SpawnMFA {
1081                module,
1082                function,
1083                arity,
1084                dest,
1085            } => {
1086                // Look up the module and function
1087                let Some(mod_ref) = self.modules.get(&module) else {
1088                    log(&format!("SpawnMFA: module {} not found", module));
1089                    return ExecResult::Crash;
1090                };
1091
1092                // Function must be exported for spawn
1093                if !mod_ref.is_exported(&function, arity) {
1094                    log(&format!(
1095                        "SpawnMFA: function {}:{}/{} not exported",
1096                        module, function, arity
1097                    ));
1098                    return ExecResult::Crash;
1099                }
1100
1101                let Some(func) = mod_ref.get_function(&function, arity) else {
1102                    log(&format!(
1103                        "SpawnMFA: function {}:{}/{} not found",
1104                        module, function, arity
1105                    ));
1106                    return ExecResult::Crash;
1107                };
1108
1109                let entry = func.entry;
1110
1111                // Copy arguments from parent's registers
1112                let Some(parent) = self.processes.get(&pid) else {
1113                    return ExecResult::Crash;
1114                };
1115                let args: Vec<Value> = (0..arity)
1116                    .map(|i| parent.registers[i as usize].clone())
1117                    .collect();
1118
1119                // Create new process
1120                let child_pid = Pid(self.next_pid);
1121                self.next_pid += 1;
1122
1123                let mut child =
1124                    Process::new_with_module(child_pid, Some(pid), module.clone(), entry);
1125
1126                // Initialize child's registers with arguments
1127                for (i, arg) in args.into_iter().enumerate() {
1128                    child.registers[i] = arg;
1129                }
1130
1131                self.processes.insert(child_pid, child);
1132                self.ready_queue.push_back(child_pid);
1133
1134                // Store child PID in parent's dest register
1135                if let Some(p) = self.processes.get_mut(&pid) {
1136                    p.registers[dest.0 as usize] = Value::Pid(child_pid);
1137                }
1138
1139                ExecResult::Continue(1)
1140            }
1141
1142            Instruction::SpawnLinkMFA {
1143                module,
1144                function,
1145                arity,
1146                dest,
1147            } => {
1148                // Look up the module and function
1149                let Some(mod_ref) = self.modules.get(&module) else {
1150                    log(&format!("SpawnLinkMFA: module {} not found", module));
1151                    return ExecResult::Crash;
1152                };
1153
1154                // Function must be exported for spawn
1155                if !mod_ref.is_exported(&function, arity) {
1156                    log(&format!(
1157                        "SpawnLinkMFA: function {}:{}/{} not exported",
1158                        module, function, arity
1159                    ));
1160                    return ExecResult::Crash;
1161                }
1162
1163                let Some(func) = mod_ref.get_function(&function, arity) else {
1164                    log(&format!(
1165                        "SpawnLinkMFA: function {}:{}/{} not found",
1166                        module, function, arity
1167                    ));
1168                    return ExecResult::Crash;
1169                };
1170
1171                let entry = func.entry;
1172
1173                // Copy arguments from parent's registers
1174                let Some(parent) = self.processes.get(&pid) else {
1175                    return ExecResult::Crash;
1176                };
1177                let args: Vec<Value> = (0..arity)
1178                    .map(|i| parent.registers[i as usize].clone())
1179                    .collect();
1180
1181                // Create new process
1182                let child_pid = Pid(self.next_pid);
1183                self.next_pid += 1;
1184
1185                let mut child =
1186                    Process::new_with_module(child_pid, Some(pid), module.clone(), entry);
1187
1188                // Initialize child's registers with arguments
1189                for (i, arg) in args.into_iter().enumerate() {
1190                    child.registers[i] = arg;
1191                }
1192
1193                // Establish bidirectional link
1194                child.links.push(pid);
1195
1196                self.processes.insert(child_pid, child);
1197                self.ready_queue.push_back(child_pid);
1198
1199                // Add link to parent and store child PID
1200                if let Some(p) = self.processes.get_mut(&pid) {
1201                    p.links.push(child_pid);
1202                    p.registers[dest.0 as usize] = Value::Pid(child_pid);
1203                }
1204
1205                ExecResult::Continue(1)
1206            }
1207
1208            Instruction::Push { source } => {
1209                let Some(process) = self.processes.get(&pid) else {
1210                    return ExecResult::Crash;
1211                };
1212                let value = match &source {
1213                    Operand::Int(n) => Value::Int(*n),
1214                    Operand::Reg(r) => process.registers[r.0 as usize].clone(),
1215                };
1216                if let Some(p) = self.processes.get_mut(&pid) {
1217                    p.stack.push(value);
1218                }
1219                ExecResult::Continue(1)
1220            }
1221
1222            Instruction::Pop { dest } => {
1223                let Some(process) = self.processes.get_mut(&pid) else {
1224                    return ExecResult::Crash;
1225                };
1226                if let Some(value) = process.stack.pop() {
1227                    process.registers[dest.0 as usize] = value;
1228                    ExecResult::Continue(1)
1229                } else {
1230                    // Empty stack - crash
1231                    ExecResult::Crash
1232                }
1233            }
1234
1235            Instruction::LoadAtom { name, dest } => {
1236                let Some(process) = self.processes.get_mut(&pid) else {
1237                    return ExecResult::Crash;
1238                };
1239                process.registers[dest.0 as usize] = Value::Atom(name.clone());
1240                ExecResult::Continue(1)
1241            }
1242
1243            Instruction::MakeTuple { arity, dest } => {
1244                let Some(process) = self.processes.get_mut(&pid) else {
1245                    return ExecResult::Crash;
1246                };
1247                let arity = arity as usize;
1248                if process.stack.len() < arity {
1249                    return ExecResult::Crash;
1250                }
1251                // Drain elements - first pushed is at lower index, which becomes first tuple element
1252                let elements: Vec<Value> =
1253                    process.stack.drain(process.stack.len() - arity..).collect();
1254                process.registers[dest.0 as usize] = Value::Tuple(elements);
1255                ExecResult::Continue(1)
1256            }
1257
1258            Instruction::TupleElement { tuple, index, dest } => {
1259                let Some(process) = self.processes.get_mut(&pid) else {
1260                    return ExecResult::Crash;
1261                };
1262                let Value::Tuple(elements) = &process.registers[tuple.0 as usize] else {
1263                    return ExecResult::Crash;
1264                };
1265                let idx = index as usize;
1266                if idx >= elements.len() {
1267                    return ExecResult::Crash;
1268                }
1269                let value = elements[idx].clone();
1270                process.registers[dest.0 as usize] = value;
1271                ExecResult::Continue(1)
1272            }
1273
1274            Instruction::TupleArity { tuple, dest } => {
1275                let Some(process) = self.processes.get_mut(&pid) else {
1276                    return ExecResult::Crash;
1277                };
1278                let Value::Tuple(elements) = &process.registers[tuple.0 as usize] else {
1279                    return ExecResult::Crash;
1280                };
1281                let arity = elements.len() as i64;
1282                process.registers[dest.0 as usize] = Value::Int(arity);
1283                ExecResult::Continue(1)
1284            }
1285
1286            Instruction::Match {
1287                source,
1288                pattern,
1289                fail_target,
1290            } => {
1291                let Some(process) = self.processes.get(&pid) else {
1292                    return ExecResult::Crash;
1293                };
1294                let value = process.registers[source.0 as usize].clone();
1295
1296                // Try to match and collect bindings
1297                let mut bindings = Vec::new();
1298                if Self::match_pattern(&value, &pattern, &mut bindings) {
1299                    // Match succeeded - apply bindings
1300                    if let Some(p) = self.processes.get_mut(&pid) {
1301                        for (reg, val) in bindings {
1302                            p.registers[reg.0 as usize] = val;
1303                        }
1304                    }
1305                    ExecResult::Continue(1)
1306                } else {
1307                    // Match failed - jump to fail target
1308                    ExecResult::Jump(fail_target, 1)
1309                }
1310            }
1311
1312            Instruction::ReceiveMatch {
1313                clauses,
1314                timeout,
1315                timeout_target,
1316            } => {
1317                let Some(process) = self.processes.get(&pid) else {
1318                    return ExecResult::Crash;
1319                };
1320
1321                // Check for timeout first
1322                if process.timeout == Some(0) {
1323                    if let Some(p) = self.processes.get_mut(&pid) {
1324                        p.timeout = None;
1325                    }
1326                    return ExecResult::Jump(timeout_target, 1);
1327                }
1328
1329                // Scan mailbox for a matching message
1330                let mailbox_len = process.mailbox.len();
1331                for i in 0..mailbox_len {
1332                    let msg = &process.mailbox[i];
1333                    let value = Self::message_to_value(msg.clone());
1334
1335                    // Try each clause
1336                    for (pattern, target) in clauses.iter() {
1337                        let mut bindings = Vec::new();
1338                        if Self::match_pattern(&value, pattern, &mut bindings) {
1339                            // Match found! Remove message and apply bindings
1340                            if let Some(p) = self.processes.get_mut(&pid) {
1341                                p.mailbox.remove(i);
1342                                p.timeout = None;
1343                                for (reg, val) in bindings {
1344                                    p.registers[reg.0 as usize] = val;
1345                                }
1346                            }
1347                            return ExecResult::Jump(*target, 1);
1348                        }
1349                    }
1350                }
1351
1352                // No match found - set timeout and wait
1353                if let Some(p) = self.processes.get_mut(&pid) {
1354                    if p.timeout.is_none() {
1355                        p.timeout = timeout.clone();
1356                    }
1357                }
1358                ExecResult::Wait
1359            }
1360
1361            Instruction::MakeList { length, dest } => {
1362                let Some(process) = self.processes.get_mut(&pid) else {
1363                    return ExecResult::Crash;
1364                };
1365                let length = length as usize;
1366                if process.stack.len() < length {
1367                    return ExecResult::Crash;
1368                }
1369                // Drain elements - first pushed is at lower index, which becomes first list element
1370                let elements: Vec<Value> = process
1371                    .stack
1372                    .drain(process.stack.len() - length..)
1373                    .collect();
1374                process.registers[dest.0 as usize] = Value::List(elements);
1375                ExecResult::Continue(1)
1376            }
1377
1378            Instruction::Cons { head, tail, dest } => {
1379                let Some(process) = self.processes.get_mut(&pid) else {
1380                    return ExecResult::Crash;
1381                };
1382                let head_val = process.registers[head.0 as usize].clone();
1383                let Value::List(mut elements) = process.registers[tail.0 as usize].clone() else {
1384                    return ExecResult::Crash;
1385                };
1386                elements.insert(0, head_val);
1387                process.registers[dest.0 as usize] = Value::List(elements);
1388                ExecResult::Continue(1)
1389            }
1390
1391            Instruction::ListHead { list, dest } => {
1392                let Some(process) = self.processes.get_mut(&pid) else {
1393                    return ExecResult::Crash;
1394                };
1395                let Value::List(elements) = &process.registers[list.0 as usize] else {
1396                    return ExecResult::Crash;
1397                };
1398                if elements.is_empty() {
1399                    return ExecResult::Crash;
1400                }
1401                let head = elements[0].clone();
1402                process.registers[dest.0 as usize] = head;
1403                ExecResult::Continue(1)
1404            }
1405
1406            Instruction::ListTail { list, dest } => {
1407                let Some(process) = self.processes.get_mut(&pid) else {
1408                    return ExecResult::Crash;
1409                };
1410                let Value::List(elements) = &process.registers[list.0 as usize] else {
1411                    return ExecResult::Crash;
1412                };
1413                if elements.is_empty() {
1414                    return ExecResult::Crash;
1415                }
1416                let tail = elements[1..].to_vec();
1417                process.registers[dest.0 as usize] = Value::List(tail);
1418                ExecResult::Continue(1)
1419            }
1420
1421            Instruction::ListIsEmpty { list, dest } => {
1422                let Some(process) = self.processes.get_mut(&pid) else {
1423                    return ExecResult::Crash;
1424                };
1425                let Value::List(elements) = &process.registers[list.0 as usize] else {
1426                    return ExecResult::Crash;
1427                };
1428                let is_empty = if elements.is_empty() { 1 } else { 0 };
1429                process.registers[dest.0 as usize] = Value::Int(is_empty);
1430                ExecResult::Continue(1)
1431            }
1432
1433            Instruction::ListLength { list, dest } => {
1434                let Some(process) = self.processes.get_mut(&pid) else {
1435                    return ExecResult::Crash;
1436                };
1437                let Value::List(elements) = &process.registers[list.0 as usize] else {
1438                    return ExecResult::Crash;
1439                };
1440                let length = elements.len() as i64;
1441                process.registers[dest.0 as usize] = Value::Int(length);
1442                ExecResult::Continue(1)
1443            }
1444
1445            Instruction::ListAppend { a, b, dest } => {
1446                let Some(process) = self.processes.get_mut(&pid) else {
1447                    return ExecResult::Crash;
1448                };
1449                let Value::List(list_a) = &process.registers[a.0 as usize] else {
1450                    return ExecResult::Crash;
1451                };
1452                let Value::List(list_b) = &process.registers[b.0 as usize] else {
1453                    return ExecResult::Crash;
1454                };
1455                let mut result = list_a.clone();
1456                result.extend(list_b.clone());
1457                process.registers[dest.0 as usize] = Value::List(result);
1458                ExecResult::Continue(1)
1459            }
1460
1461            Instruction::ListReverse { list, dest } => {
1462                let Some(process) = self.processes.get_mut(&pid) else {
1463                    return ExecResult::Crash;
1464                };
1465                let Value::List(elements) = &process.registers[list.0 as usize] else {
1466                    return ExecResult::Crash;
1467                };
1468                let reversed: Vec<Value> = elements.iter().rev().cloned().collect();
1469                process.registers[dest.0 as usize] = Value::List(reversed);
1470                ExecResult::Continue(1)
1471            }
1472
1473            Instruction::ListNth { list, n, dest } => {
1474                let Some(process) = self.processes.get_mut(&pid) else {
1475                    return ExecResult::Crash;
1476                };
1477                let Value::List(elements) = &process.registers[list.0 as usize] else {
1478                    return ExecResult::Crash;
1479                };
1480                let Value::Int(index) = process.registers[n.0 as usize] else {
1481                    return ExecResult::Crash;
1482                };
1483                if index < 0 || index as usize >= elements.len() {
1484                    return ExecResult::Crash;
1485                }
1486                let elem = elements[index as usize].clone();
1487                process.registers[dest.0 as usize] = elem;
1488                ExecResult::Continue(1)
1489            }
1490
1491            Instruction::ListMember { elem, list, dest } => {
1492                let Some(process) = self.processes.get_mut(&pid) else {
1493                    return ExecResult::Crash;
1494                };
1495                let Value::List(elements) = &process.registers[list.0 as usize] else {
1496                    return ExecResult::Crash;
1497                };
1498                let search_elem = &process.registers[elem.0 as usize];
1499                let found = elements.contains(search_elem);
1500                process.registers[dest.0 as usize] = Value::Int(if found { 1 } else { 0 });
1501                ExecResult::Continue(1)
1502            }
1503
1504            Instruction::IsInteger { source, dest } => {
1505                let Some(process) = self.processes.get_mut(&pid) else {
1506                    return ExecResult::Crash;
1507                };
1508                let is_int = matches!(process.registers[source.0 as usize], Value::Int(_));
1509                process.registers[dest.0 as usize] = Value::Int(if is_int { 1 } else { 0 });
1510                ExecResult::Continue(1)
1511            }
1512
1513            Instruction::IsAtom { source, dest } => {
1514                let Some(process) = self.processes.get_mut(&pid) else {
1515                    return ExecResult::Crash;
1516                };
1517                let is_atom = matches!(process.registers[source.0 as usize], Value::Atom(_));
1518                process.registers[dest.0 as usize] = Value::Int(if is_atom { 1 } else { 0 });
1519                ExecResult::Continue(1)
1520            }
1521
1522            Instruction::IsTuple { source, dest } => {
1523                let Some(process) = self.processes.get_mut(&pid) else {
1524                    return ExecResult::Crash;
1525                };
1526                let is_tuple = matches!(process.registers[source.0 as usize], Value::Tuple(_));
1527                process.registers[dest.0 as usize] = Value::Int(if is_tuple { 1 } else { 0 });
1528                ExecResult::Continue(1)
1529            }
1530
1531            Instruction::IsList { source, dest } => {
1532                let Some(process) = self.processes.get_mut(&pid) else {
1533                    return ExecResult::Crash;
1534                };
1535                let is_list = matches!(process.registers[source.0 as usize], Value::List(_));
1536                process.registers[dest.0 as usize] = Value::Int(if is_list { 1 } else { 0 });
1537                ExecResult::Continue(1)
1538            }
1539
1540            Instruction::IsPid { source, dest } => {
1541                let Some(process) = self.processes.get_mut(&pid) else {
1542                    return ExecResult::Crash;
1543                };
1544                let is_pid = matches!(process.registers[source.0 as usize], Value::Pid(_));
1545                process.registers[dest.0 as usize] = Value::Int(if is_pid { 1 } else { 0 });
1546                ExecResult::Continue(1)
1547            }
1548
1549            Instruction::IsFunction { source, dest } => {
1550                let Some(process) = self.processes.get_mut(&pid) else {
1551                    return ExecResult::Crash;
1552                };
1553                let is_fun = matches!(
1554                    process.registers[source.0 as usize],
1555                    Value::Fun { .. } | Value::Closure { .. }
1556                );
1557                process.registers[dest.0 as usize] = Value::Int(if is_fun { 1 } else { 0 });
1558                ExecResult::Continue(1)
1559            }
1560
1561            Instruction::IsString { source, dest } => {
1562                let Some(process) = self.processes.get_mut(&pid) else {
1563                    return ExecResult::Crash;
1564                };
1565                let is_string = matches!(process.registers[source.0 as usize], Value::String(_));
1566                process.registers[dest.0 as usize] = Value::Int(if is_string { 1 } else { 0 });
1567                ExecResult::Continue(1)
1568            }
1569
1570            // ========== Process Dictionary ==========
1571            Instruction::PutDict { key, value, dest } => {
1572                let Some(process) = self.processes.get_mut(&pid) else {
1573                    return ExecResult::Crash;
1574                };
1575                let key_val = process.registers[key.0 as usize].clone();
1576                let new_val = process.registers[value.0 as usize].clone();
1577                let old_val = process.dictionary.insert(key_val, new_val);
1578                process.registers[dest.0 as usize] = old_val.unwrap_or(Value::None);
1579                ExecResult::Continue(1)
1580            }
1581
1582            Instruction::GetDict { key, dest } => {
1583                let Some(process) = self.processes.get_mut(&pid) else {
1584                    return ExecResult::Crash;
1585                };
1586                let key_val = process.registers[key.0 as usize].clone();
1587                let value = process
1588                    .dictionary
1589                    .get(&key_val)
1590                    .cloned()
1591                    .unwrap_or(Value::None);
1592                process.registers[dest.0 as usize] = value;
1593                ExecResult::Continue(1)
1594            }
1595
1596            Instruction::EraseDict { key, dest } => {
1597                let Some(process) = self.processes.get_mut(&pid) else {
1598                    return ExecResult::Crash;
1599                };
1600                let key_val = process.registers[key.0 as usize].clone();
1601                let old_val = process.dictionary.remove(&key_val);
1602                process.registers[dest.0 as usize] = old_val.unwrap_or(Value::None);
1603                ExecResult::Continue(1)
1604            }
1605
1606            Instruction::GetDictKeys { dest } => {
1607                let Some(process) = self.processes.get_mut(&pid) else {
1608                    return ExecResult::Crash;
1609                };
1610                let keys: Vec<Value> = process.dictionary.keys().cloned().collect();
1611                process.registers[dest.0 as usize] = Value::List(keys);
1612                ExecResult::Continue(1)
1613            }
1614
1615            // ========== Maps ==========
1616            Instruction::MakeMap { count, dest } => {
1617                let Some(process) = self.processes.get_mut(&pid) else {
1618                    return ExecResult::Crash;
1619                };
1620                let mut map = std::collections::HashMap::new();
1621                // Pop count pairs from stack (in reverse: v, k, v, k, ...)
1622                for _ in 0..count {
1623                    let Some(value) = process.stack.pop() else {
1624                        return ExecResult::Crash;
1625                    };
1626                    let Some(key) = process.stack.pop() else {
1627                        return ExecResult::Crash;
1628                    };
1629                    map.insert(key, value);
1630                }
1631                process.registers[dest.0 as usize] = Value::Map(map);
1632                ExecResult::Continue(1)
1633            }
1634
1635            Instruction::MapGet { map, key, dest } => {
1636                let Some(process) = self.processes.get_mut(&pid) else {
1637                    return ExecResult::Crash;
1638                };
1639                let Value::Map(m) = &process.registers[map.0 as usize] else {
1640                    return ExecResult::Crash;
1641                };
1642                let key_val = &process.registers[key.0 as usize];
1643                let Some(value) = m.get(key_val) else {
1644                    return ExecResult::Crash; // Key not found
1645                };
1646                let value = value.clone();
1647                process.registers[dest.0 as usize] = value;
1648                ExecResult::Continue(1)
1649            }
1650
1651            Instruction::MapGetDefault {
1652                map,
1653                key,
1654                default,
1655                dest,
1656            } => {
1657                let Some(process) = self.processes.get_mut(&pid) else {
1658                    return ExecResult::Crash;
1659                };
1660                let Value::Map(m) = &process.registers[map.0 as usize] else {
1661                    return ExecResult::Crash;
1662                };
1663                let key_val = &process.registers[key.0 as usize];
1664                let value = m
1665                    .get(key_val)
1666                    .cloned()
1667                    .unwrap_or_else(|| process.registers[default.0 as usize].clone());
1668                process.registers[dest.0 as usize] = value;
1669                ExecResult::Continue(1)
1670            }
1671
1672            Instruction::MapPut {
1673                map,
1674                key,
1675                value,
1676                dest,
1677            } => {
1678                let Some(process) = self.processes.get_mut(&pid) else {
1679                    return ExecResult::Crash;
1680                };
1681                let Value::Map(m) = &process.registers[map.0 as usize] else {
1682                    return ExecResult::Crash;
1683                };
1684                let mut new_map = m.clone();
1685                let key_val = process.registers[key.0 as usize].clone();
1686                let val = process.registers[value.0 as usize].clone();
1687                new_map.insert(key_val, val);
1688                process.registers[dest.0 as usize] = Value::Map(new_map);
1689                ExecResult::Continue(1)
1690            }
1691
1692            Instruction::MapRemove { map, key, dest } => {
1693                let Some(process) = self.processes.get_mut(&pid) else {
1694                    return ExecResult::Crash;
1695                };
1696                let Value::Map(m) = &process.registers[map.0 as usize] else {
1697                    return ExecResult::Crash;
1698                };
1699                let mut new_map = m.clone();
1700                let key_val = &process.registers[key.0 as usize];
1701                new_map.remove(key_val);
1702                process.registers[dest.0 as usize] = Value::Map(new_map);
1703                ExecResult::Continue(1)
1704            }
1705
1706            Instruction::MapHas { map, key, dest } => {
1707                let Some(process) = self.processes.get_mut(&pid) else {
1708                    return ExecResult::Crash;
1709                };
1710                let Value::Map(m) = &process.registers[map.0 as usize] else {
1711                    return ExecResult::Crash;
1712                };
1713                let key_val = &process.registers[key.0 as usize];
1714                let has = m.contains_key(key_val);
1715                process.registers[dest.0 as usize] = Value::Int(if has { 1 } else { 0 });
1716                ExecResult::Continue(1)
1717            }
1718
1719            Instruction::MapSize { map, dest } => {
1720                let Some(process) = self.processes.get_mut(&pid) else {
1721                    return ExecResult::Crash;
1722                };
1723                let Value::Map(m) = &process.registers[map.0 as usize] else {
1724                    return ExecResult::Crash;
1725                };
1726                let size = m.len() as i64;
1727                process.registers[dest.0 as usize] = Value::Int(size);
1728                ExecResult::Continue(1)
1729            }
1730
1731            Instruction::MapKeys { map, dest } => {
1732                let Some(process) = self.processes.get_mut(&pid) else {
1733                    return ExecResult::Crash;
1734                };
1735                let Value::Map(m) = &process.registers[map.0 as usize] else {
1736                    return ExecResult::Crash;
1737                };
1738                let keys: Vec<Value> = m.keys().cloned().collect();
1739                process.registers[dest.0 as usize] = Value::List(keys);
1740                ExecResult::Continue(1)
1741            }
1742
1743            Instruction::MapValues { map, dest } => {
1744                let Some(process) = self.processes.get_mut(&pid) else {
1745                    return ExecResult::Crash;
1746                };
1747                let Value::Map(m) = &process.registers[map.0 as usize] else {
1748                    return ExecResult::Crash;
1749                };
1750                let values: Vec<Value> = m.values().cloned().collect();
1751                process.registers[dest.0 as usize] = Value::List(values);
1752                ExecResult::Continue(1)
1753            }
1754
1755            Instruction::IsMap { source, dest } => {
1756                let Some(process) = self.processes.get_mut(&pid) else {
1757                    return ExecResult::Crash;
1758                };
1759                let is_map = matches!(process.registers[source.0 as usize], Value::Map(_));
1760                process.registers[dest.0 as usize] = Value::Int(if is_map { 1 } else { 0 });
1761                ExecResult::Continue(1)
1762            }
1763
1764            // ========== Binaries ==========
1765            Instruction::MakeBinary { bytes, dest } => {
1766                let Some(process) = self.processes.get_mut(&pid) else {
1767                    return ExecResult::Crash;
1768                };
1769                process.registers[dest.0 as usize] = Value::Binary(bytes.clone());
1770                ExecResult::Continue(1)
1771            }
1772
1773            Instruction::BinarySize { bin, dest } => {
1774                let Some(process) = self.processes.get_mut(&pid) else {
1775                    return ExecResult::Crash;
1776                };
1777                let Value::Binary(bytes) = &process.registers[bin.0 as usize] else {
1778                    return ExecResult::Crash;
1779                };
1780                process.registers[dest.0 as usize] = Value::Int(bytes.len() as i64);
1781                ExecResult::Continue(1)
1782            }
1783
1784            Instruction::BinaryAt { bin, index, dest } => {
1785                let Some(process) = self.processes.get_mut(&pid) else {
1786                    return ExecResult::Crash;
1787                };
1788                let Value::Binary(bytes) = &process.registers[bin.0 as usize] else {
1789                    return ExecResult::Crash;
1790                };
1791                let Value::Int(idx) = process.registers[index.0 as usize] else {
1792                    return ExecResult::Crash;
1793                };
1794                if idx < 0 || idx as usize >= bytes.len() {
1795                    return ExecResult::Crash;
1796                }
1797                process.registers[dest.0 as usize] = Value::Int(bytes[idx as usize] as i64);
1798                ExecResult::Continue(1)
1799            }
1800
1801            Instruction::BinarySlice {
1802                bin,
1803                start,
1804                len,
1805                dest,
1806            } => {
1807                let Some(process) = self.processes.get_mut(&pid) else {
1808                    return ExecResult::Crash;
1809                };
1810                let Value::Binary(bytes) = &process.registers[bin.0 as usize] else {
1811                    return ExecResult::Crash;
1812                };
1813                let Value::Int(start_idx) = process.registers[start.0 as usize] else {
1814                    return ExecResult::Crash;
1815                };
1816                let Value::Int(length) = process.registers[len.0 as usize] else {
1817                    return ExecResult::Crash;
1818                };
1819                if start_idx < 0 || length < 0 {
1820                    return ExecResult::Crash;
1821                }
1822                let start_idx = start_idx as usize;
1823                let length = length as usize;
1824                if start_idx + length > bytes.len() {
1825                    return ExecResult::Crash;
1826                }
1827                let slice = bytes[start_idx..start_idx + length].to_vec();
1828                process.registers[dest.0 as usize] = Value::Binary(slice);
1829                ExecResult::Continue(1)
1830            }
1831
1832            Instruction::BinaryConcat { a, b, dest } => {
1833                let Some(process) = self.processes.get_mut(&pid) else {
1834                    return ExecResult::Crash;
1835                };
1836                let Value::Binary(bytes_a) = &process.registers[a.0 as usize] else {
1837                    return ExecResult::Crash;
1838                };
1839                let Value::Binary(bytes_b) = &process.registers[b.0 as usize] else {
1840                    return ExecResult::Crash;
1841                };
1842                let mut result = bytes_a.clone();
1843                result.extend_from_slice(bytes_b);
1844                process.registers[dest.0 as usize] = Value::Binary(result);
1845                ExecResult::Continue(1)
1846            }
1847
1848            Instruction::IsBinary { source, dest } => {
1849                let Some(process) = self.processes.get_mut(&pid) else {
1850                    return ExecResult::Crash;
1851                };
1852                let is_binary = matches!(process.registers[source.0 as usize], Value::Binary(_));
1853                process.registers[dest.0 as usize] = Value::Int(if is_binary { 1 } else { 0 });
1854                ExecResult::Continue(1)
1855            }
1856
1857            Instruction::StringToBinary { source, dest } => {
1858                let Some(process) = self.processes.get_mut(&pid) else {
1859                    return ExecResult::Crash;
1860                };
1861                let Value::String(s) = &process.registers[source.0 as usize] else {
1862                    return ExecResult::Crash;
1863                };
1864                let bytes = s.as_bytes().to_vec();
1865                process.registers[dest.0 as usize] = Value::Binary(bytes);
1866                ExecResult::Continue(1)
1867            }
1868
1869            Instruction::BinaryToString { source, dest } => {
1870                let Some(process) = self.processes.get_mut(&pid) else {
1871                    return ExecResult::Crash;
1872                };
1873                let Value::Binary(bytes) = &process.registers[source.0 as usize] else {
1874                    return ExecResult::Crash;
1875                };
1876                let Ok(s) = std::str::from_utf8(bytes) else {
1877                    return ExecResult::Crash;
1878                };
1879                process.registers[dest.0 as usize] = Value::String(s.to_string());
1880                ExecResult::Continue(1)
1881            }
1882
1883            // ========== Bit Syntax ==========
1884            Instruction::BinaryConstructSegments { segments, dest } => {
1885                let Some(process) = self.processes.get(&pid) else {
1886                    return ExecResult::Crash;
1887                };
1888
1889                let mut result = Vec::new();
1890
1891                for (source, segment) in segments {
1892                    let value = match source {
1893                        crate::SegmentSource::Int(n) => n,
1894                        crate::SegmentSource::Reg(r) => {
1895                            match &process.registers[r.0 as usize] {
1896                                Value::Int(n) => *n,
1897                                Value::Binary(bytes) => {
1898                                    // Binary segment: append the bytes directly
1899                                    if segment.bit_type == crate::BitType::Binary {
1900                                        result.extend_from_slice(bytes);
1901                                        continue;
1902                                    }
1903                                    return ExecResult::Crash;
1904                                }
1905                                _ => return ExecResult::Crash,
1906                            }
1907                        }
1908                    };
1909
1910                    match segment.bit_type {
1911                        crate::BitType::Integer => {
1912                            let size_bits = segment.size.unwrap_or(8);
1913                            let size_bytes = (size_bits as usize + 7) / 8;
1914
1915                            // Convert to bytes based on endianness
1916                            let bytes = match segment.endianness {
1917                                crate::Endianness::Big => {
1918                                    let mut bytes = Vec::with_capacity(size_bytes);
1919                                    for i in (0..size_bytes).rev() {
1920                                        bytes.push(((value >> (i * 8)) & 0xFF) as u8);
1921                                    }
1922                                    bytes
1923                                }
1924                                crate::Endianness::Little => {
1925                                    let mut bytes = Vec::with_capacity(size_bytes);
1926                                    for i in 0..size_bytes {
1927                                        bytes.push(((value >> (i * 8)) & 0xFF) as u8);
1928                                    }
1929                                    bytes
1930                                }
1931                            };
1932                            result.extend(bytes);
1933                        }
1934                        crate::BitType::Float => {
1935                            let size_bits = segment.size.unwrap_or(64);
1936                            let f = value as f64;
1937                            if size_bits == 64 {
1938                                let bytes = match segment.endianness {
1939                                    crate::Endianness::Big => f.to_be_bytes(),
1940                                    crate::Endianness::Little => f.to_le_bytes(),
1941                                };
1942                                result.extend_from_slice(&bytes);
1943                            } else if size_bits == 32 {
1944                                let f32_val = f as f32;
1945                                let bytes = match segment.endianness {
1946                                    crate::Endianness::Big => f32_val.to_be_bytes(),
1947                                    crate::Endianness::Little => f32_val.to_le_bytes(),
1948                                };
1949                                result.extend_from_slice(&bytes);
1950                            } else {
1951                                return ExecResult::Crash;
1952                            }
1953                        }
1954                        crate::BitType::Binary => {
1955                            // Already handled above for register source
1956                            return ExecResult::Crash;
1957                        }
1958                        crate::BitType::Utf8 => {
1959                            // Encode the integer as a UTF-8 codepoint
1960                            if let Some(c) = char::from_u32(value as u32) {
1961                                let mut buf = [0u8; 4];
1962                                let s = c.encode_utf8(&mut buf);
1963                                result.extend_from_slice(s.as_bytes());
1964                            } else {
1965                                return ExecResult::Crash;
1966                            }
1967                        }
1968                    }
1969                }
1970
1971                let Some(process) = self.processes.get_mut(&pid) else {
1972                    return ExecResult::Crash;
1973                };
1974                process.registers[dest.0 as usize] = Value::Binary(result);
1975                ExecResult::Continue(1)
1976            }
1977
1978            Instruction::BinaryMatchStart { source } => {
1979                let Some(process) = self.processes.get_mut(&pid) else {
1980                    return ExecResult::Crash;
1981                };
1982                let Value::Binary(bytes) = &process.registers[source.0 as usize] else {
1983                    return ExecResult::Crash;
1984                };
1985                let bytes_clone = bytes.clone();
1986                process.binary_match_state = Some((bytes_clone, 0));
1987                ExecResult::Continue(1)
1988            }
1989
1990            Instruction::BinaryMatchSegment {
1991                segment,
1992                dest,
1993                fail_target,
1994            } => {
1995                let Some(process) = self.processes.get_mut(&pid) else {
1996                    return ExecResult::Crash;
1997                };
1998
1999                let Some((ref bytes, ref mut bit_pos)) = process.binary_match_state else {
2000                    return ExecResult::Jump(fail_target, 1);
2001                };
2002
2003                let size_bits = match segment.size {
2004                    Some(s) => s as usize,
2005                    None => {
2006                        // For binary type with no size, match rest
2007                        if segment.bit_type == crate::BitType::Binary {
2008                            (bytes.len() * 8).saturating_sub(*bit_pos)
2009                        } else {
2010                            return ExecResult::Jump(fail_target, 1);
2011                        }
2012                    }
2013                };
2014
2015                // Check we have enough bits
2016                let remaining_bits = bytes.len() * 8 - *bit_pos;
2017                if size_bits > remaining_bits {
2018                    return ExecResult::Jump(fail_target, 1);
2019                }
2020
2021                // For now, only support byte-aligned access
2022                if *bit_pos % 8 != 0 || size_bits % 8 != 0 {
2023                    // TODO: implement bit-level access
2024                    return ExecResult::Jump(fail_target, 1);
2025                }
2026
2027                let byte_pos = *bit_pos / 8;
2028                let size_bytes = size_bits / 8;
2029
2030                let value = match segment.bit_type {
2031                    crate::BitType::Integer => {
2032                        let mut n: i64 = 0;
2033                        match segment.endianness {
2034                            crate::Endianness::Big => {
2035                                for i in 0..size_bytes {
2036                                    n = (n << 8) | (bytes[byte_pos + i] as i64);
2037                                }
2038                            }
2039                            crate::Endianness::Little => {
2040                                for i in (0..size_bytes).rev() {
2041                                    n = (n << 8) | (bytes[byte_pos + i] as i64);
2042                                }
2043                            }
2044                        }
2045                        // Handle signedness
2046                        if segment.signedness == crate::Signedness::Signed && size_bytes < 8 {
2047                            let sign_bit = 1i64 << (size_bits - 1);
2048                            if n & sign_bit != 0 {
2049                                n |= !((1i64 << size_bits) - 1);
2050                            }
2051                        }
2052                        Value::Int(n)
2053                    }
2054                    crate::BitType::Float => {
2055                        if size_bits == 64 {
2056                            let mut arr = [0u8; 8];
2057                            arr.copy_from_slice(&bytes[byte_pos..byte_pos + 8]);
2058                            let f = match segment.endianness {
2059                                crate::Endianness::Big => f64::from_be_bytes(arr),
2060                                crate::Endianness::Little => f64::from_le_bytes(arr),
2061                            };
2062                            Value::Float(f)
2063                        } else if size_bits == 32 {
2064                            let mut arr = [0u8; 4];
2065                            arr.copy_from_slice(&bytes[byte_pos..byte_pos + 4]);
2066                            let f = match segment.endianness {
2067                                crate::Endianness::Big => f32::from_be_bytes(arr) as f64,
2068                                crate::Endianness::Little => f32::from_le_bytes(arr) as f64,
2069                            };
2070                            Value::Float(f)
2071                        } else {
2072                            return ExecResult::Jump(fail_target, 1);
2073                        }
2074                    }
2075                    crate::BitType::Binary => {
2076                        Value::Binary(bytes[byte_pos..byte_pos + size_bytes].to_vec())
2077                    }
2078                    crate::BitType::Utf8 => {
2079                        // Try to decode a UTF-8 codepoint
2080                        let slice = &bytes[byte_pos..];
2081                        if let Some(c) = std::str::from_utf8(slice)
2082                            .ok()
2083                            .and_then(|s| s.chars().next())
2084                        {
2085                            let char_len = c.len_utf8();
2086                            if char_len <= slice.len() {
2087                                // Update size_bits to reflect actual UTF-8 character length
2088                                *bit_pos += char_len * 8;
2089                                process.registers[dest.0 as usize] = Value::Int(c as i64);
2090                                return ExecResult::Continue(1);
2091                            }
2092                        }
2093                        return ExecResult::Jump(fail_target, 1);
2094                    }
2095                };
2096
2097                *bit_pos += size_bits;
2098                process.registers[dest.0 as usize] = value;
2099                ExecResult::Continue(1)
2100            }
2101
2102            Instruction::BinaryMatchRest { dest } => {
2103                let Some(process) = self.processes.get_mut(&pid) else {
2104                    return ExecResult::Crash;
2105                };
2106
2107                let Some((ref bytes, bit_pos)) = process.binary_match_state else {
2108                    return ExecResult::Crash;
2109                };
2110
2111                // Only support byte-aligned rest
2112                if bit_pos % 8 != 0 {
2113                    return ExecResult::Crash;
2114                }
2115
2116                let byte_pos = bit_pos / 8;
2117                let rest = bytes[byte_pos..].to_vec();
2118                process.registers[dest.0 as usize] = Value::Binary(rest);
2119                ExecResult::Continue(1)
2120            }
2121
2122            Instruction::BinaryGetInteger {
2123                bin,
2124                bit_offset,
2125                segment,
2126                dest,
2127            } => {
2128                let Some(process) = self.processes.get(&pid) else {
2129                    return ExecResult::Crash;
2130                };
2131
2132                let Value::Binary(bytes) = &process.registers[bin.0 as usize] else {
2133                    return ExecResult::Crash;
2134                };
2135                let Value::Int(offset) = &process.registers[bit_offset.0 as usize] else {
2136                    return ExecResult::Crash;
2137                };
2138
2139                let bit_pos = *offset as usize;
2140                let size_bits = segment.size.unwrap_or(8) as usize;
2141
2142                // Only support byte-aligned access for now
2143                if bit_pos % 8 != 0 || size_bits % 8 != 0 {
2144                    return ExecResult::Crash;
2145                }
2146
2147                let byte_pos = bit_pos / 8;
2148                let size_bytes = size_bits / 8;
2149
2150                if byte_pos + size_bytes > bytes.len() {
2151                    return ExecResult::Crash;
2152                }
2153
2154                let mut n: i64 = 0;
2155                match segment.endianness {
2156                    crate::Endianness::Big => {
2157                        for i in 0..size_bytes {
2158                            n = (n << 8) | (bytes[byte_pos + i] as i64);
2159                        }
2160                    }
2161                    crate::Endianness::Little => {
2162                        for i in (0..size_bytes).rev() {
2163                            n = (n << 8) | (bytes[byte_pos + i] as i64);
2164                        }
2165                    }
2166                }
2167
2168                // Handle signedness
2169                if segment.signedness == crate::Signedness::Signed && size_bytes < 8 {
2170                    let sign_bit = 1i64 << (size_bits - 1);
2171                    if n & sign_bit != 0 {
2172                        n |= !((1i64 << size_bits) - 1);
2173                    }
2174                }
2175
2176                let Some(process) = self.processes.get_mut(&pid) else {
2177                    return ExecResult::Crash;
2178                };
2179                process.registers[dest.0 as usize] = Value::Int(n);
2180                ExecResult::Continue(1)
2181            }
2182
2183            Instruction::BinaryPutInteger {
2184                bin,
2185                bit_offset,
2186                value,
2187                segment,
2188                dest,
2189            } => {
2190                let Some(process) = self.processes.get(&pid) else {
2191                    return ExecResult::Crash;
2192                };
2193
2194                let Value::Binary(bytes) = &process.registers[bin.0 as usize] else {
2195                    return ExecResult::Crash;
2196                };
2197                let Value::Int(offset) = &process.registers[bit_offset.0 as usize] else {
2198                    return ExecResult::Crash;
2199                };
2200                let Value::Int(val) = &process.registers[value.0 as usize] else {
2201                    return ExecResult::Crash;
2202                };
2203
2204                let bit_pos = *offset as usize;
2205                let size_bits = segment.size.unwrap_or(8) as usize;
2206
2207                // Only support byte-aligned access for now
2208                if bit_pos % 8 != 0 || size_bits % 8 != 0 {
2209                    return ExecResult::Crash;
2210                }
2211
2212                let byte_pos = bit_pos / 8;
2213                let size_bytes = size_bits / 8;
2214
2215                // Create new binary with value inserted
2216                let mut new_bytes = bytes.clone();
2217
2218                // Extend if needed
2219                while new_bytes.len() < byte_pos + size_bytes {
2220                    new_bytes.push(0);
2221                }
2222
2223                match segment.endianness {
2224                    crate::Endianness::Big => {
2225                        for i in 0..size_bytes {
2226                            new_bytes[byte_pos + i] =
2227                                ((val >> ((size_bytes - 1 - i) * 8)) & 0xFF) as u8;
2228                        }
2229                    }
2230                    crate::Endianness::Little => {
2231                        for i in 0..size_bytes {
2232                            new_bytes[byte_pos + i] = ((val >> (i * 8)) & 0xFF) as u8;
2233                        }
2234                    }
2235                }
2236
2237                let Some(process) = self.processes.get_mut(&pid) else {
2238                    return ExecResult::Crash;
2239                };
2240                process.registers[dest.0 as usize] = Value::Binary(new_bytes);
2241                ExecResult::Continue(1)
2242            }
2243
2244            // ========== References ==========
2245            Instruction::MakeRef { dest } => {
2246                let ref_id = self.next_ref;
2247                self.next_ref += 1;
2248                let Some(process) = self.processes.get_mut(&pid) else {
2249                    return ExecResult::Crash;
2250                };
2251                process.registers[dest.0 as usize] = Value::Ref(ref_id);
2252                ExecResult::Continue(1)
2253            }
2254
2255            Instruction::IsRef { source, dest } => {
2256                let Some(process) = self.processes.get_mut(&pid) else {
2257                    return ExecResult::Crash;
2258                };
2259                let is_ref = matches!(process.registers[source.0 as usize], Value::Ref(_));
2260                process.registers[dest.0 as usize] = Value::Int(if is_ref { 1 } else { 0 });
2261                ExecResult::Continue(1)
2262            }
2263
2264            // ========== Floats ==========
2265            Instruction::LoadFloat { value, dest } => {
2266                let Some(process) = self.processes.get_mut(&pid) else {
2267                    return ExecResult::Crash;
2268                };
2269                process.registers[dest.0 as usize] = Value::Float(value);
2270                ExecResult::Continue(1)
2271            }
2272
2273            Instruction::IsFloat { source, dest } => {
2274                let Some(process) = self.processes.get_mut(&pid) else {
2275                    return ExecResult::Crash;
2276                };
2277                let is_float = matches!(process.registers[source.0 as usize], Value::Float(_));
2278                process.registers[dest.0 as usize] = Value::Int(if is_float { 1 } else { 0 });
2279                ExecResult::Continue(1)
2280            }
2281
2282            Instruction::IntToFloat { source, dest } => {
2283                let Some(process) = self.processes.get_mut(&pid) else {
2284                    return ExecResult::Crash;
2285                };
2286                let Value::Int(n) = process.registers[source.0 as usize] else {
2287                    return ExecResult::Crash;
2288                };
2289                process.registers[dest.0 as usize] = Value::Float(n as f64);
2290                ExecResult::Continue(1)
2291            }
2292
2293            Instruction::FloatToInt { source, dest } => {
2294                let Some(process) = self.processes.get_mut(&pid) else {
2295                    return ExecResult::Crash;
2296                };
2297                let Value::Float(f) = process.registers[source.0 as usize] else {
2298                    return ExecResult::Crash;
2299                };
2300                process.registers[dest.0 as usize] = Value::Int(f as i64);
2301                ExecResult::Continue(1)
2302            }
2303
2304            Instruction::Floor { source, dest } => {
2305                let Some(process) = self.processes.get_mut(&pid) else {
2306                    return ExecResult::Crash;
2307                };
2308                let Value::Float(f) = process.registers[source.0 as usize] else {
2309                    return ExecResult::Crash;
2310                };
2311                process.registers[dest.0 as usize] = Value::Float(f.floor());
2312                ExecResult::Continue(1)
2313            }
2314
2315            Instruction::Ceil { source, dest } => {
2316                let Some(process) = self.processes.get_mut(&pid) else {
2317                    return ExecResult::Crash;
2318                };
2319                let Value::Float(f) = process.registers[source.0 as usize] else {
2320                    return ExecResult::Crash;
2321                };
2322                process.registers[dest.0 as usize] = Value::Float(f.ceil());
2323                ExecResult::Continue(1)
2324            }
2325
2326            Instruction::Round { source, dest } => {
2327                let Some(process) = self.processes.get_mut(&pid) else {
2328                    return ExecResult::Crash;
2329                };
2330                let Value::Float(f) = process.registers[source.0 as usize] else {
2331                    return ExecResult::Crash;
2332                };
2333                process.registers[dest.0 as usize] = Value::Float(f.round());
2334                ExecResult::Continue(1)
2335            }
2336
2337            Instruction::Trunc { source, dest } => {
2338                let Some(process) = self.processes.get_mut(&pid) else {
2339                    return ExecResult::Crash;
2340                };
2341                let Value::Float(f) = process.registers[source.0 as usize] else {
2342                    return ExecResult::Crash;
2343                };
2344                process.registers[dest.0 as usize] = Value::Float(f.trunc());
2345                ExecResult::Continue(1)
2346            }
2347
2348            Instruction::Sqrt { source, dest } => {
2349                let Some(process) = self.processes.get_mut(&pid) else {
2350                    return ExecResult::Crash;
2351                };
2352                let f = match process.registers[source.0 as usize] {
2353                    Value::Float(f) => f,
2354                    Value::Int(n) => n as f64,
2355                    _ => return ExecResult::Crash,
2356                };
2357                process.registers[dest.0 as usize] = Value::Float(f.sqrt());
2358                ExecResult::Continue(1)
2359            }
2360
2361            Instruction::Abs { source, dest } => {
2362                let Some(process) = self.processes.get_mut(&pid) else {
2363                    return ExecResult::Crash;
2364                };
2365                let result = match process.registers[source.0 as usize] {
2366                    Value::Float(f) => Value::Float(f.abs()),
2367                    Value::Int(n) => Value::Int(n.abs()),
2368                    _ => return ExecResult::Crash,
2369                };
2370                process.registers[dest.0 as usize] = result;
2371                ExecResult::Continue(1)
2372            }
2373
2374            Instruction::Pow { base, exp, dest } => {
2375                let Some(process) = self.processes.get_mut(&pid) else {
2376                    return ExecResult::Crash;
2377                };
2378                let b = match process.registers[base.0 as usize] {
2379                    Value::Float(f) => f,
2380                    Value::Int(n) => n as f64,
2381                    _ => return ExecResult::Crash,
2382                };
2383                let e = match process.registers[exp.0 as usize] {
2384                    Value::Float(f) => f,
2385                    Value::Int(n) => n as f64,
2386                    _ => return ExecResult::Crash,
2387                };
2388                process.registers[dest.0 as usize] = Value::Float(b.powf(e));
2389                ExecResult::Continue(1)
2390            }
2391
2392            // ========== Timers ==========
2393            Instruction::SendAfter {
2394                delay,
2395                to,
2396                msg,
2397                dest,
2398            } => {
2399                let Some(process) = self.processes.get(&pid) else {
2400                    return ExecResult::Crash;
2401                };
2402                let Some(target_pid) = self.resolve_pid(pid, &to) else {
2403                    // Target process not found
2404                    return ExecResult::Crash;
2405                };
2406                let message = process.registers[msg.0 as usize].clone();
2407
2408                // Create timer reference
2409                let timer_ref = self.next_ref;
2410                self.next_ref += 1;
2411
2412                // Add timer to queue
2413                self.timers.push(Timer {
2414                    timer_ref,
2415                    target: target_pid,
2416                    message,
2417                    remaining: delay,
2418                });
2419
2420                // Store timer ref in dest register
2421                let Some(process) = self.processes.get_mut(&pid) else {
2422                    return ExecResult::Crash;
2423                };
2424                process.registers[dest.0 as usize] = Value::Ref(timer_ref);
2425                ExecResult::Continue(1)
2426            }
2427
2428            Instruction::StartTimer { delay, msg, dest } => {
2429                let Some(process) = self.processes.get(&pid) else {
2430                    return ExecResult::Crash;
2431                };
2432                let message_value = process.registers[msg.0 as usize].clone();
2433
2434                // Create timer reference
2435                let timer_ref = self.next_ref;
2436                self.next_ref += 1;
2437
2438                // Build {:timeout, ref, msg} tuple
2439                let timeout_msg = Value::Tuple(vec![
2440                    Value::Atom("timeout".to_string()),
2441                    Value::Ref(timer_ref),
2442                    message_value,
2443                ]);
2444
2445                // Add timer to queue (sends to self)
2446                self.timers.push(Timer {
2447                    timer_ref,
2448                    target: pid,
2449                    message: timeout_msg,
2450                    remaining: delay,
2451                });
2452
2453                // Store timer ref in dest register
2454                let Some(process) = self.processes.get_mut(&pid) else {
2455                    return ExecResult::Crash;
2456                };
2457                process.registers[dest.0 as usize] = Value::Ref(timer_ref);
2458                ExecResult::Continue(1)
2459            }
2460
2461            Instruction::CancelTimer { timer_ref, dest } => {
2462                let Some(process) = self.processes.get(&pid) else {
2463                    return ExecResult::Crash;
2464                };
2465                let Value::Ref(ref_id) = process.registers[timer_ref.0 as usize] else {
2466                    return ExecResult::Crash;
2467                };
2468
2469                // Find and remove the timer
2470                let result =
2471                    if let Some(idx) = self.timers.iter().position(|t| t.timer_ref == ref_id) {
2472                        let timer = self.timers.remove(idx);
2473                        Value::Int(timer.remaining as i64)
2474                    } else {
2475                        // Timer already fired or doesn't exist
2476                        Value::Atom("ok".to_string())
2477                    };
2478
2479                let Some(process) = self.processes.get_mut(&pid) else {
2480                    return ExecResult::Crash;
2481                };
2482                process.registers[dest.0 as usize] = result;
2483                ExecResult::Continue(1)
2484            }
2485
2486            Instruction::ReadTimer { timer_ref, dest } => {
2487                let Some(process) = self.processes.get(&pid) else {
2488                    return ExecResult::Crash;
2489                };
2490                let Value::Ref(ref_id) = process.registers[timer_ref.0 as usize] else {
2491                    return ExecResult::Crash;
2492                };
2493
2494                // Find the timer and get remaining time
2495                let remaining = self
2496                    .timers
2497                    .iter()
2498                    .find(|t| t.timer_ref == ref_id)
2499                    .map(|t| Value::Int(t.remaining as i64))
2500                    .unwrap_or(Value::Int(0));
2501
2502                let Some(process) = self.processes.get_mut(&pid) else {
2503                    return ExecResult::Crash;
2504                };
2505                process.registers[dest.0 as usize] = remaining;
2506                ExecResult::Continue(1)
2507            }
2508
2509            // ========== IO ==========
2510            Instruction::PrintLn { source } => {
2511                let Some(process) = self.processes.get(&pid) else {
2512                    return ExecResult::Crash;
2513                };
2514                let value = &process.registers[source.0 as usize];
2515                // Capture output for testing, or print to stdout
2516                self.output.push(format!("{:?}", value));
2517                #[cfg(not(test))]
2518                println!("{:?}", value);
2519                ExecResult::Continue(1)
2520            }
2521
2522            Instruction::ReadLine { dest } => {
2523                let Some(process) = self.processes.get_mut(&pid) else {
2524                    return ExecResult::Crash;
2525                };
2526                // In tests/WASM, return :eof. In CLI, read from stdin.
2527                #[cfg(test)]
2528                {
2529                    process.registers[dest.0 as usize] = Value::Atom("eof".to_string());
2530                }
2531                #[cfg(not(test))]
2532                {
2533                    use std::io::BufRead;
2534                    let stdin = std::io::stdin();
2535                    let mut line = String::new();
2536                    match stdin.lock().read_line(&mut line) {
2537                        Ok(0) => {
2538                            process.registers[dest.0 as usize] = Value::Atom("eof".to_string());
2539                        }
2540                        Ok(_) => {
2541                            // Trim trailing newline
2542                            let trimmed = line.trim_end_matches('\n').trim_end_matches('\r');
2543                            process.registers[dest.0 as usize] = Value::String(trimmed.to_string());
2544                        }
2545                        Err(_) => {
2546                            process.registers[dest.0 as usize] = Value::Atom("eof".to_string());
2547                        }
2548                    }
2549                }
2550                ExecResult::Continue(1)
2551            }
2552
2553            Instruction::FileRead { path, dest } => {
2554                let Some(process) = self.processes.get_mut(&pid) else {
2555                    return ExecResult::Crash;
2556                };
2557                let path_str = match &process.registers[path.0 as usize] {
2558                    Value::String(s) => s.clone(),
2559                    _ => return ExecResult::Crash,
2560                };
2561                match std::fs::read(&path_str) {
2562                    Ok(bytes) => {
2563                        process.registers[dest.0 as usize] =
2564                            Value::Tuple(vec![Value::Atom("ok".to_string()), Value::Binary(bytes)]);
2565                    }
2566                    Err(e) => {
2567                        process.registers[dest.0 as usize] = Value::Tuple(vec![
2568                            Value::Atom("error".to_string()),
2569                            Value::Atom(e.kind().to_string()),
2570                        ]);
2571                    }
2572                }
2573                ExecResult::Continue(1)
2574            }
2575
2576            Instruction::FileWrite {
2577                path,
2578                content,
2579                dest,
2580            } => {
2581                let Some(process) = self.processes.get_mut(&pid) else {
2582                    return ExecResult::Crash;
2583                };
2584                let path_str = match &process.registers[path.0 as usize] {
2585                    Value::String(s) => s.clone(),
2586                    _ => return ExecResult::Crash,
2587                };
2588                let bytes = match &process.registers[content.0 as usize] {
2589                    Value::Binary(b) => b.clone(),
2590                    Value::String(s) => s.as_bytes().to_vec(),
2591                    _ => return ExecResult::Crash,
2592                };
2593                match std::fs::write(&path_str, bytes) {
2594                    Ok(()) => {
2595                        process.registers[dest.0 as usize] = Value::Atom("ok".to_string());
2596                    }
2597                    Err(e) => {
2598                        process.registers[dest.0 as usize] = Value::Tuple(vec![
2599                            Value::Atom("error".to_string()),
2600                            Value::Atom(e.kind().to_string()),
2601                        ]);
2602                    }
2603                }
2604                ExecResult::Continue(1)
2605            }
2606
2607            Instruction::FileExists { path, dest } => {
2608                let Some(process) = self.processes.get_mut(&pid) else {
2609                    return ExecResult::Crash;
2610                };
2611                let path_str = match &process.registers[path.0 as usize] {
2612                    Value::String(s) => s.clone(),
2613                    _ => return ExecResult::Crash,
2614                };
2615                let exists = std::path::Path::new(&path_str).exists();
2616                process.registers[dest.0 as usize] = Value::Int(if exists { 1 } else { 0 });
2617                ExecResult::Continue(1)
2618            }
2619
2620            Instruction::FileDelete { path, dest } => {
2621                let Some(process) = self.processes.get_mut(&pid) else {
2622                    return ExecResult::Crash;
2623                };
2624                let path_str = match &process.registers[path.0 as usize] {
2625                    Value::String(s) => s.clone(),
2626                    _ => return ExecResult::Crash,
2627                };
2628                match std::fs::remove_file(&path_str) {
2629                    Ok(()) => {
2630                        process.registers[dest.0 as usize] = Value::Atom("ok".to_string());
2631                    }
2632                    Err(e) => {
2633                        process.registers[dest.0 as usize] = Value::Tuple(vec![
2634                            Value::Atom("error".to_string()),
2635                            Value::Atom(e.kind().to_string()),
2636                        ]);
2637                    }
2638                }
2639                ExecResult::Continue(1)
2640            }
2641
2642            // ========== System Info ==========
2643            Instruction::SelfPid { dest } => {
2644                let Some(process) = self.processes.get_mut(&pid) else {
2645                    return ExecResult::Crash;
2646                };
2647                process.registers[dest.0 as usize] = Value::Pid(pid);
2648                ExecResult::Continue(1)
2649            }
2650
2651            Instruction::ProcessList { dest } => {
2652                let pids: Vec<Value> = self.processes.keys().map(|p| Value::Pid(*p)).collect();
2653                let Some(process) = self.processes.get_mut(&pid) else {
2654                    return ExecResult::Crash;
2655                };
2656                process.registers[dest.0 as usize] = Value::List(pids);
2657                ExecResult::Continue(1)
2658            }
2659
2660            Instruction::ProcessCount { dest } => {
2661                let count = self.processes.len() as i64;
2662                let Some(process) = self.processes.get_mut(&pid) else {
2663                    return ExecResult::Crash;
2664                };
2665                process.registers[dest.0 as usize] = Value::Int(count);
2666                ExecResult::Continue(1)
2667            }
2668
2669            Instruction::IsAlive {
2670                pid: target_pid,
2671                dest,
2672            } => {
2673                let Some(process) = self.processes.get(&pid) else {
2674                    return ExecResult::Crash;
2675                };
2676                let target = match &process.registers[target_pid.0 as usize] {
2677                    Value::Pid(p) => *p,
2678                    _ => return ExecResult::Crash,
2679                };
2680                let alive = self.processes.contains_key(&target);
2681                let Some(process) = self.processes.get_mut(&pid) else {
2682                    return ExecResult::Crash;
2683                };
2684                process.registers[dest.0 as usize] = Value::Int(if alive { 1 } else { 0 });
2685                ExecResult::Continue(1)
2686            }
2687
2688            Instruction::ProcessInfo {
2689                pid: target_pid,
2690                dest,
2691            } => {
2692                let Some(process) = self.processes.get(&pid) else {
2693                    return ExecResult::Crash;
2694                };
2695                let target = match &process.registers[target_pid.0 as usize] {
2696                    Value::Pid(p) => *p,
2697                    _ => return ExecResult::Crash,
2698                };
2699                let info = if let Some(target_proc) = self.processes.get(&target) {
2700                    let status_atom = match target_proc.status {
2701                        ProcessStatus::Ready => "ready",
2702                        ProcessStatus::Waiting => "waiting",
2703                        ProcessStatus::Done => "done",
2704                        ProcessStatus::Crashed => "crashed",
2705                    };
2706                    Value::Tuple(vec![
2707                        Value::Atom(status_atom.to_string()),
2708                        Value::Int(target_proc.mailbox.len() as i64),
2709                        Value::Int(target_proc.links.len() as i64),
2710                        Value::Int(target_proc.monitors.len() as i64),
2711                        Value::Int(if target_proc.trap_exit { 1 } else { 0 }),
2712                    ])
2713                } else {
2714                    Value::Atom("undefined".to_string())
2715                };
2716                let Some(process) = self.processes.get_mut(&pid) else {
2717                    return ExecResult::Crash;
2718                };
2719                process.registers[dest.0 as usize] = info;
2720                ExecResult::Continue(1)
2721            }
2722
2723            Instruction::ModuleList { dest } => {
2724                let modules: Vec<Value> = self
2725                    .modules
2726                    .keys()
2727                    .map(|name| Value::Atom(name.clone()))
2728                    .collect();
2729                let Some(process) = self.processes.get_mut(&pid) else {
2730                    return ExecResult::Crash;
2731                };
2732                process.registers[dest.0 as usize] = Value::List(modules);
2733                ExecResult::Continue(1)
2734            }
2735
2736            Instruction::FunctionExported {
2737                module,
2738                function,
2739                arity,
2740                dest,
2741            } => {
2742                let Some(process) = self.processes.get(&pid) else {
2743                    return ExecResult::Crash;
2744                };
2745                let mod_name = match &process.registers[module.0 as usize] {
2746                    Value::Atom(s) => s.clone(),
2747                    Value::String(s) => s.clone(),
2748                    _ => return ExecResult::Crash,
2749                };
2750                let func_name = match &process.registers[function.0 as usize] {
2751                    Value::Atom(s) => s.clone(),
2752                    Value::String(s) => s.clone(),
2753                    _ => return ExecResult::Crash,
2754                };
2755                let func_arity = match &process.registers[arity.0 as usize] {
2756                    Value::Int(n) => *n as u8,
2757                    _ => return ExecResult::Crash,
2758                };
2759                let exported = self
2760                    .modules
2761                    .get(&mod_name)
2762                    .map(|m| m.is_exported(&func_name, func_arity))
2763                    .unwrap_or(false);
2764                let Some(process) = self.processes.get_mut(&pid) else {
2765                    return ExecResult::Crash;
2766                };
2767                process.registers[dest.0 as usize] = Value::Int(if exported { 1 } else { 0 });
2768                ExecResult::Continue(1)
2769            }
2770
2771            // ========== Exception Handling ==========
2772            Instruction::Try {
2773                catch_target,
2774                after_target,
2775            } => {
2776                let Some(process) = self.processes.get_mut(&pid) else {
2777                    return ExecResult::Crash;
2778                };
2779                // Push exception handler onto try stack
2780                process.try_stack.push(TryFrame {
2781                    catch_target,
2782                    after_target,
2783                    call_stack_depth: process.call_stack.len(),
2784                    stack_depth: process.stack.len(),
2785                });
2786                ExecResult::Continue(1)
2787            }
2788
2789            Instruction::EndTry => {
2790                let Some(process) = self.processes.get_mut(&pid) else {
2791                    return ExecResult::Crash;
2792                };
2793                // Pop the try frame
2794                if let Some(frame) = process.try_stack.pop() {
2795                    // If there's an after block, jump to it
2796                    if let Some(after_target) = frame.after_target {
2797                        return ExecResult::Jump(after_target, 1);
2798                    }
2799                }
2800                ExecResult::Continue(1)
2801            }
2802
2803            Instruction::Throw { class, reason } => {
2804                let Some(process) = self.processes.get(&pid) else {
2805                    return ExecResult::Crash;
2806                };
2807                let class_val = process.registers[class.0 as usize].clone();
2808                let reason_val = process.registers[reason.0 as usize].clone();
2809                ExecResult::Throw(class_val, reason_val)
2810            }
2811
2812            Instruction::GetException { dest } => {
2813                let Some(process) = self.processes.get_mut(&pid) else {
2814                    return ExecResult::Crash;
2815                };
2816                let exception_tuple =
2817                    if let Some((class, reason, stacktrace)) = &process.current_exception {
2818                        // Convert stacktrace to a list of strings
2819                        let stacktrace_list = Value::List(
2820                            stacktrace
2821                                .iter()
2822                                .map(|s| Value::String(s.clone()))
2823                                .collect(),
2824                        );
2825                        Value::Tuple(vec![class.clone(), reason.clone(), stacktrace_list])
2826                    } else {
2827                        Value::None
2828                    };
2829                process.registers[dest.0 as usize] = exception_tuple;
2830                ExecResult::Continue(1)
2831            }
2832
2833            Instruction::ClearException => {
2834                let Some(process) = self.processes.get_mut(&pid) else {
2835                    return ExecResult::Crash;
2836                };
2837                process.current_exception = None;
2838                ExecResult::Continue(1)
2839            }
2840
2841            Instruction::Reraise => {
2842                let Some(process) = self.processes.get(&pid) else {
2843                    return ExecResult::Crash;
2844                };
2845                if let Some((class, reason, _)) = &process.current_exception {
2846                    ExecResult::Throw(class.clone(), reason.clone())
2847                } else {
2848                    // No exception to reraise, crash
2849                    ExecResult::Crash
2850                }
2851            }
2852        }
2853    }
2854
2855    /// Helper for arithmetic operations with BigInt support and overflow promotion
2856    fn bigint_arith_op<F, G>(
2857        &mut self,
2858        pid: Pid,
2859        a: &Operand,
2860        b: &Operand,
2861        dest: crate::Register,
2862        checked_op: F,
2863        bigint_op: G,
2864    ) -> ExecResult
2865    where
2866        F: Fn(i64, i64) -> Option<Value>,
2867        G: Fn(BigInt, BigInt) -> BigInt,
2868    {
2869        let Some(process) = self.processes.get(&pid) else {
2870            return ExecResult::Crash;
2871        };
2872        let av = self.resolve_operand_value(process, a);
2873        let bv = self.resolve_operand_value(process, b);
2874        match (av, bv) {
2875            (Some(Value::Int(x)), Some(Value::Int(y))) => {
2876                // Try checked operation first, promote to BigInt on overflow
2877                let result = checked_op(x, y).unwrap_or_else(|| {
2878                    Value::from_bigint(bigint_op(BigInt::from(x), BigInt::from(y)))
2879                });
2880                if let Some(p) = self.processes.get_mut(&pid) {
2881                    p.registers[dest.0 as usize] = result;
2882                }
2883                ExecResult::Continue(1)
2884            }
2885            (Some(a_val), Some(b_val)) => {
2886                // At least one is BigInt, use BigInt arithmetic
2887                match (a_val.to_bigint(), b_val.to_bigint()) {
2888                    (Some(x), Some(y)) => {
2889                        let result = Value::from_bigint(bigint_op(x, y));
2890                        if let Some(p) = self.processes.get_mut(&pid) {
2891                            p.registers[dest.0 as usize] = result;
2892                        }
2893                        ExecResult::Continue(1)
2894                    }
2895                    _ => ExecResult::Crash,
2896                }
2897            }
2898            _ => ExecResult::Crash,
2899        }
2900    }
2901
2902    /// Helper for comparison operations with BigInt support
2903    fn bigint_cmp_op<F>(
2904        &mut self,
2905        pid: Pid,
2906        a: &Operand,
2907        b: &Operand,
2908        dest: crate::Register,
2909        op: F,
2910    ) -> ExecResult
2911    where
2912        F: Fn(&BigInt, &BigInt) -> bool,
2913    {
2914        let Some(process) = self.processes.get(&pid) else {
2915            return ExecResult::Crash;
2916        };
2917        let av = self.resolve_operand_value(process, a);
2918        let bv = self.resolve_operand_value(process, b);
2919        match (av, bv) {
2920            (Some(a_val), Some(b_val)) => match (a_val.to_bigint(), b_val.to_bigint()) {
2921                (Some(x), Some(y)) => {
2922                    let result = if op(&x, &y) { 1 } else { 0 };
2923                    if let Some(p) = self.processes.get_mut(&pid) {
2924                        p.registers[dest.0 as usize] = Value::Int(result);
2925                    }
2926                    ExecResult::Continue(1)
2927                }
2928                _ => ExecResult::Crash,
2929            },
2930            _ => ExecResult::Crash,
2931        }
2932    }
2933
2934    /// Resolve an operand to a Value (supports Int and BigInt)
2935    fn resolve_operand_value(&self, process: &Process, operand: &Operand) -> Option<Value> {
2936        match operand {
2937            Operand::Int(n) => Some(Value::Int(*n)),
2938            Operand::Reg(r) => {
2939                let val = &process.registers[r.0 as usize];
2940                match val {
2941                    Value::Int(_) | Value::BigInt(_) => Some(val.clone()),
2942                    _ => None,
2943                }
2944            }
2945        }
2946    }
2947
2948    /// Resolve an operand to an integer value (legacy, for code that only needs i64)
2949    fn resolve_operand(&self, process: &Process, operand: &Operand) -> Option<i64> {
2950        match operand {
2951            Operand::Int(n) => Some(*n),
2952            Operand::Reg(r) => process.registers[r.0 as usize].as_int(),
2953        }
2954    }
2955
2956    fn resolve_pid(&self, current: Pid, source: &Source) -> Option<Pid> {
2957        match source {
2958            Source::Self_ => Some(current),
2959            Source::Parent => {
2960                let process = self.processes.get(&current)?;
2961                process.parent
2962            }
2963            Source::Pid(p) => Some(*p),
2964            Source::Reg(r) => {
2965                let process = self.processes.get(&current)?;
2966                match &process.registers[r.0 as usize] {
2967                    Value::Pid(p) => Some(*p),
2968                    _ => None,
2969                }
2970            }
2971            Source::Named(name) => self.registry.get(name).copied(),
2972        }
2973    }
2974
2975    fn resolve_source(&self, process: &Process, source: &Source) -> Value {
2976        match source {
2977            Source::Self_ => Value::Pid(process.pid),
2978            Source::Parent => process.parent.map(Value::Pid).unwrap_or(Value::None),
2979            Source::Pid(p) => Value::Pid(*p),
2980            Source::Reg(r) => process.registers[r.0 as usize].clone(),
2981            Source::Named(name) => self
2982                .registry
2983                .get(name)
2984                .map(|p| Value::Pid(*p))
2985                .unwrap_or(Value::None),
2986        }
2987    }
2988
2989    fn message_to_value(msg: Message) -> Value {
2990        match msg {
2991            Message::User(s) => Value::String(s),
2992            Message::System(sys) => match sys {
2993                SystemMsg::Exit(pid, reason) => {
2994                    // {:EXIT, Pid, Reason}
2995                    Value::Tuple(vec![
2996                        Value::Atom("EXIT".to_string()),
2997                        Value::Pid(pid),
2998                        reason,
2999                    ])
3000                }
3001                SystemMsg::Down(ref_id, pid, reason) => {
3002                    // {:DOWN, Ref, :process, Pid, Reason}
3003                    Value::Tuple(vec![
3004                        Value::Atom("DOWN".to_string()),
3005                        Value::Ref(ref_id),
3006                        Value::Atom("process".to_string()),
3007                        Value::Pid(pid),
3008                        reason,
3009                    ])
3010                }
3011            },
3012        }
3013    }
3014
3015    /// Try to match a value against a pattern, collecting variable bindings
3016    /// Returns true if match succeeds, false otherwise
3017    fn match_pattern(
3018        value: &Value,
3019        pattern: &Pattern,
3020        bindings: &mut Vec<(Register, Value)>,
3021    ) -> bool {
3022        match pattern {
3023            Pattern::Wildcard => true,
3024
3025            Pattern::Variable(reg) => {
3026                bindings.push((*reg, value.clone()));
3027                true
3028            }
3029
3030            Pattern::Int(n) => matches!(value, Value::Int(v) if v == n),
3031
3032            Pattern::Atom(name) => matches!(value, Value::Atom(a) if a == name),
3033
3034            Pattern::String(s) => matches!(value, Value::String(v) if v == s),
3035
3036            Pattern::Binary(bytes) => matches!(value, Value::Binary(b) if b == bytes),
3037
3038            Pattern::Tuple(patterns) => {
3039                let Value::Tuple(elements) = value else {
3040                    return false;
3041                };
3042                if elements.len() != patterns.len() {
3043                    return false;
3044                }
3045                for (elem, pat) in elements.iter().zip(patterns.iter()) {
3046                    if !Self::match_pattern(elem, pat, bindings) {
3047                        return false;
3048                    }
3049                }
3050                true
3051            }
3052
3053            Pattern::ListEmpty => {
3054                matches!(value, Value::List(elements) if elements.is_empty())
3055            }
3056
3057            Pattern::ListCons { head, tail } => {
3058                let Value::List(elements) = value else {
3059                    return false;
3060                };
3061                if elements.is_empty() {
3062                    return false;
3063                }
3064                // Match head against first element
3065                if !Self::match_pattern(&elements[0], head, bindings) {
3066                    return false;
3067                }
3068                // Match tail against rest of list
3069                let tail_value = Value::List(elements[1..].to_vec());
3070                Self::match_pattern(&tail_value, tail, bindings)
3071            }
3072
3073            Pattern::Map(pairs) => {
3074                let Value::Map(map) = value else {
3075                    return false;
3076                };
3077                // For each key-value pattern pair, find matching entry
3078                for (key_pattern, value_pattern) in pairs {
3079                    // Find a map entry where key matches the key pattern
3080                    let mut found = false;
3081                    for (k, v) in map.iter() {
3082                        let mut key_bindings = Vec::new();
3083                        if Self::match_pattern(k, key_pattern, &mut key_bindings) {
3084                            // Key matches, now check if value matches
3085                            let mut val_bindings = Vec::new();
3086                            if Self::match_pattern(v, value_pattern, &mut val_bindings) {
3087                                // Both match, add bindings
3088                                bindings.extend(key_bindings);
3089                                bindings.extend(val_bindings);
3090                                found = true;
3091                                break;
3092                            }
3093                        }
3094                    }
3095                    if !found {
3096                        return false;
3097                    }
3098                }
3099                true
3100            }
3101        }
3102    }
3103
3104    fn finish_process_with_reason(&mut self, pid: Pid, status: ProcessStatus, reason: Value) {
3105        let (links, monitors) = if let Some(p) = self.processes.get_mut(&pid) {
3106            p.status = status;
3107            p.exit_reason = reason.clone();
3108            (p.links.clone(), p.monitored_by.clone())
3109        } else {
3110            return;
3111        };
3112
3113        // Remove any registry entries for this process
3114        self.registry.retain(|_, v| *v != pid);
3115
3116        // Determine if this is a "normal" exit
3117        let is_normal = reason == Value::Atom("normal".to_string());
3118
3119        // Handle linked processes
3120        // - Normal exit: only notify if they trap_exit (send {:EXIT, Pid, :normal})
3121        // - Abnormal exit: if they trap_exit, send message; otherwise crash them
3122        let mut to_crash = Vec::new();
3123        for linked_pid in links {
3124            if let Some(linked) = self.processes.get_mut(&linked_pid) {
3125                // Remove the link from the linked process
3126                linked.links.retain(|p| *p != pid);
3127
3128                if linked.trap_exit {
3129                    // Convert exit signal to message: {:EXIT, Pid, Reason}
3130                    let exit_tuple = Value::Tuple(vec![
3131                        Value::Atom("EXIT".to_string()),
3132                        Value::Pid(pid),
3133                        reason.clone(),
3134                    ]);
3135                    linked
3136                        .mailbox
3137                        .push_back(Message::System(SystemMsg::Exit(pid, reason.clone())));
3138                    // Also add as user message for pattern matching in receive
3139                    linked
3140                        .mailbox
3141                        .push_back(Message::User(format!("{:?}", exit_tuple)));
3142                    if linked.status == ProcessStatus::Waiting {
3143                        linked.status = ProcessStatus::Ready;
3144                        self.ready_queue.push_back(linked_pid);
3145                    }
3146                } else if !is_normal {
3147                    // Abnormal exit propagates to linked processes (crash them)
3148                    to_crash.push(linked_pid);
3149                }
3150                // Normal exit with trap_exit=false: do nothing (no notification)
3151            }
3152        }
3153
3154        // Crash linked processes that don't trap_exit (after releasing borrow)
3155        for linked_pid in to_crash {
3156            self.finish_process_with_reason(linked_pid, ProcessStatus::Crashed, reason.clone());
3157        }
3158
3159        // Notify monitoring processes (one-way, always send DOWN message)
3160        for (ref_id, monitor_pid) in monitors {
3161            if let Some(monitor) = self.processes.get_mut(&monitor_pid) {
3162                monitor.mailbox.push_back(Message::System(SystemMsg::Down(
3163                    ref_id,
3164                    pid,
3165                    reason.clone(),
3166                )));
3167                if monitor.status == ProcessStatus::Waiting {
3168                    monitor.status = ProcessStatus::Ready;
3169                    self.ready_queue.push_back(monitor_pid);
3170                }
3171                // Also remove the monitor from the monitoring process's list
3172                monitor.monitors.retain(|(_, target)| *target != pid);
3173            }
3174        }
3175    }
3176
3177    /// Get count of processes by status
3178    pub fn process_count(&self) -> (usize, usize, usize, usize) {
3179        let mut ready = 0;
3180        let mut waiting = 0;
3181        let mut done = 0;
3182        let mut crashed = 0;
3183
3184        for p in self.processes.values() {
3185            match p.status {
3186                ProcessStatus::Ready => ready += 1,
3187                ProcessStatus::Waiting => waiting += 1,
3188                ProcessStatus::Done => done += 1,
3189                ProcessStatus::Crashed => crashed += 1,
3190            }
3191        }
3192
3193        (ready, waiting, done, crashed)
3194    }
3195}
3196
3197impl Default for Scheduler {
3198    fn default() -> Self {
3199        Self::new()
3200    }
3201}
3202
3203#[cfg(test)]
3204mod tests {
3205    use super::*;
3206    use crate::{Operand, Register};
3207
3208    fn run_to_idle(scheduler: &mut Scheduler) {
3209        loop {
3210            if scheduler.step(100) == StepResult::Idle {
3211                break;
3212            }
3213        }
3214    }
3215
3216    #[test]
3217    fn test_spawn_and_end() {
3218        let mut scheduler = Scheduler::new();
3219
3220        let program = vec![Instruction::End];
3221        scheduler.spawn(program);
3222
3223        run_to_idle(&mut scheduler);
3224
3225        let (ready, waiting, done, crashed) = scheduler.process_count();
3226        assert_eq!((ready, waiting, done, crashed), (0, 0, 1, 0));
3227    }
3228
3229    #[test]
3230    fn test_work_uses_reductions() {
3231        let mut scheduler = Scheduler::new();
3232
3233        let program = vec![Instruction::Work { amount: 5 }, Instruction::End];
3234        scheduler.spawn(program);
3235
3236        // Step with budget of 3 - not enough to complete
3237        let result = scheduler.step(3);
3238        assert_eq!(result, StepResult::Busy);
3239
3240        // Process should still be ready
3241        let (ready, _, _, _) = scheduler.process_count();
3242        assert_eq!(ready, 1);
3243
3244        // Now finish it
3245        run_to_idle(&mut scheduler);
3246        let (_, _, done, _) = scheduler.process_count();
3247        assert_eq!(done, 1);
3248    }
3249
3250    #[test]
3251    fn test_message_passing() {
3252        let mut scheduler = Scheduler::new();
3253
3254        // Child: receive message, then end
3255        let child = vec![Instruction::Receive { dest: Register(0) }, Instruction::End];
3256
3257        // Parent: spawn child, send message, end
3258        let parent = vec![
3259            Instruction::Spawn {
3260                code: child,
3261                dest: Register(0),
3262            },
3263            Instruction::Send {
3264                to: Source::Reg(Register(0)),
3265                msg: "hello".into(),
3266            },
3267            Instruction::End,
3268        ];
3269
3270        scheduler.spawn(parent);
3271        run_to_idle(&mut scheduler);
3272
3273        let (_, _, done, _) = scheduler.process_count();
3274        assert_eq!(done, 2);
3275    }
3276
3277    #[test]
3278    fn test_parent_tracking() {
3279        let mut scheduler = Scheduler::new();
3280
3281        // Child: send message to parent, then end
3282        let child = vec![
3283            Instruction::Send {
3284                to: Source::Parent,
3285                msg: "from child".into(),
3286            },
3287            Instruction::End,
3288        ];
3289
3290        // Parent: spawn child, receive message, end
3291        let parent = vec![
3292            Instruction::Spawn {
3293                code: child,
3294                dest: Register(0),
3295            },
3296            Instruction::Receive { dest: Register(1) },
3297            Instruction::End,
3298        ];
3299
3300        scheduler.spawn(parent);
3301        run_to_idle(&mut scheduler);
3302
3303        // Check parent received the message
3304        let parent_process = scheduler.processes.get(&Pid(0)).unwrap();
3305        match &parent_process.registers[1] {
3306            Value::String(s) => assert_eq!(s, "from child"),
3307            _ => panic!("Expected string in register"),
3308        }
3309    }
3310
3311    #[test]
3312    fn test_spawn_link_crash_notification() {
3313        let mut scheduler = Scheduler::new();
3314
3315        // Child: crash immediately
3316        let child = vec![Instruction::Crash];
3317
3318        // Parent: enable trap_exit, spawn_link child, receive exit notification
3319        let parent = vec![
3320            Instruction::TrapExit { enable: true },
3321            Instruction::SpawnLink {
3322                code: child,
3323                dest: Register(0),
3324            },
3325            Instruction::Receive { dest: Register(1) },
3326            Instruction::End,
3327        ];
3328
3329        scheduler.spawn(parent);
3330        run_to_idle(&mut scheduler);
3331
3332        // Parent should have received {:EXIT, ChildPid, :crashed} tuple
3333        let parent_process = scheduler.processes.get(&Pid(0)).unwrap();
3334        match &parent_process.registers[1] {
3335            Value::Tuple(elems) => {
3336                assert_eq!(elems.len(), 3);
3337                assert_eq!(elems[0], Value::Atom("EXIT".to_string()));
3338                assert!(matches!(elems[1], Value::Pid(_)));
3339                assert_eq!(elems[2], Value::Atom("crashed".to_string()));
3340            }
3341            _ => panic!("Expected EXIT tuple, got {:?}", parent_process.registers[1]),
3342        }
3343
3344        let (_, _, done, crashed) = scheduler.process_count();
3345        assert_eq!(done, 1); // Parent done
3346        assert_eq!(crashed, 1); // Child crashed
3347    }
3348
3349    #[test]
3350    fn test_monitor_down_notification() {
3351        let mut scheduler = Scheduler::new();
3352
3353        // Worker: crash after some work
3354        let worker = vec![Instruction::Work { amount: 1 }, Instruction::Crash];
3355
3356        // Observer: spawn, monitor, wait for DOWN
3357        let observer = vec![
3358            Instruction::Spawn {
3359                code: worker,
3360                dest: Register(0),
3361            },
3362            Instruction::Monitor {
3363                target: Source::Reg(Register(0)),
3364                dest: Register(2),
3365            },
3366            Instruction::Receive { dest: Register(1) },
3367            Instruction::End,
3368        ];
3369
3370        scheduler.spawn(observer);
3371        run_to_idle(&mut scheduler);
3372
3373        // Observer should have received {:DOWN, Ref, :process, Pid, Reason} tuple
3374        let observer_process = scheduler.processes.get(&Pid(0)).unwrap();
3375        match &observer_process.registers[1] {
3376            Value::Tuple(elems) => {
3377                assert_eq!(elems.len(), 5);
3378                assert_eq!(elems[0], Value::Atom("DOWN".to_string()));
3379                assert!(matches!(elems[1], Value::Ref(_)));
3380                assert_eq!(elems[2], Value::Atom("process".to_string()));
3381                assert!(matches!(elems[3], Value::Pid(_)));
3382                assert_eq!(elems[4], Value::Atom("crashed".to_string()));
3383            }
3384            _ => panic!(
3385                "Expected DOWN tuple, got {:?}",
3386                observer_process.registers[1]
3387            ),
3388        }
3389    }
3390
3391    #[test]
3392    fn test_receive_timeout() {
3393        let mut scheduler = Scheduler::new();
3394
3395        // Process waits for message that never comes
3396        let waiter = vec![
3397            Instruction::ReceiveTimeout {
3398                dest: Register(0),
3399                timeout: 3,
3400            },
3401            Instruction::End,
3402        ];
3403
3404        scheduler.spawn(waiter);
3405
3406        // Run enough steps for timeout to expire
3407        for _ in 0..10 {
3408            if scheduler.step(1) == StepResult::Idle {
3409                break;
3410            }
3411        }
3412
3413        // Check that TIMEOUT was received
3414        let process = scheduler.processes.get(&Pid(0)).unwrap();
3415        match &process.registers[0] {
3416            Value::String(s) => assert_eq!(s, "TIMEOUT"),
3417            _ => panic!("Expected TIMEOUT string"),
3418        }
3419    }
3420
3421    #[test]
3422    fn test_process_registry() {
3423        let mut scheduler = Scheduler::new();
3424
3425        // Server: register, send ready, receive, end
3426        let server = vec![
3427            Instruction::Register {
3428                name: "myserver".into(),
3429            },
3430            Instruction::Send {
3431                to: Source::Parent,
3432                msg: "ready".into(),
3433            },
3434            Instruction::Receive { dest: Register(0) },
3435            Instruction::End,
3436        ];
3437
3438        // Client: spawn server, wait for ready, send by name
3439        let client = vec![
3440            Instruction::Spawn {
3441                code: server,
3442                dest: Register(0),
3443            },
3444            Instruction::Receive { dest: Register(1) }, // wait for ready
3445            Instruction::Send {
3446                to: Source::Named("myserver".into()),
3447                msg: "ping".into(),
3448            },
3449            Instruction::End,
3450        ];
3451
3452        scheduler.spawn(client);
3453        run_to_idle(&mut scheduler);
3454
3455        // Both should complete
3456        let (_, _, done, _) = scheduler.process_count();
3457        assert_eq!(done, 2);
3458
3459        // Server should have received "ping"
3460        let server_process = scheduler.processes.get(&Pid(1)).unwrap();
3461        match &server_process.registers[0] {
3462            Value::String(s) => assert_eq!(s, "ping"),
3463            _ => panic!("Expected ping message"),
3464        }
3465    }
3466
3467    #[test]
3468    fn test_whereis() {
3469        let mut scheduler = Scheduler::new();
3470
3471        let program = vec![
3472            Instruction::Register {
3473                name: "test".into(),
3474            },
3475            Instruction::WhereIs {
3476                name: "test".into(),
3477                dest: Register(0),
3478            },
3479            Instruction::WhereIs {
3480                name: "nonexistent".into(),
3481                dest: Register(1),
3482            },
3483            Instruction::End,
3484        ];
3485
3486        scheduler.spawn(program);
3487        run_to_idle(&mut scheduler);
3488
3489        let process = scheduler.processes.get(&Pid(0)).unwrap();
3490
3491        // Register 0 should have the PID
3492        match &process.registers[0] {
3493            Value::Pid(p) => assert_eq!(p.0, 0),
3494            _ => panic!("Expected Pid in register 0"),
3495        }
3496
3497        // Register 1 should be None
3498        match &process.registers[1] {
3499            Value::None => {}
3500            _ => panic!("Expected None in register 1"),
3501        }
3502    }
3503
3504    #[test]
3505    fn test_registry_cleanup_on_exit() {
3506        let mut scheduler = Scheduler::new();
3507
3508        let program = vec![
3509            Instruction::Register {
3510                name: "temp".into(),
3511            },
3512            Instruction::End,
3513        ];
3514
3515        scheduler.spawn(program);
3516
3517        // After one step, process registers
3518        scheduler.step(1);
3519        assert!(scheduler.registry.contains_key("temp"));
3520
3521        // After completion, registry should be cleaned
3522        run_to_idle(&mut scheduler);
3523        assert!(!scheduler.registry.contains_key("temp"));
3524    }
3525
3526    #[test]
3527    fn test_spawn_many_processes() {
3528        let mut scheduler = Scheduler::new();
3529
3530        let worker = vec![Instruction::End];
3531
3532        // Spawner creates 50 workers
3533        let mut spawner = Vec::new();
3534        for _ in 0..50 {
3535            spawner.push(Instruction::Spawn {
3536                code: worker.clone(),
3537                dest: Register(0),
3538            });
3539        }
3540        spawner.push(Instruction::End);
3541
3542        scheduler.spawn(spawner);
3543        run_to_idle(&mut scheduler);
3544
3545        // Should have 51 processes total (spawner + 50 workers)
3546        assert_eq!(scheduler.processes.len(), 51);
3547
3548        let (_, _, done, _) = scheduler.process_count();
3549        assert_eq!(done, 51);
3550    }
3551
3552    #[test]
3553    fn test_print_captures_output() {
3554        let mut scheduler = Scheduler::new();
3555
3556        let program = vec![
3557            Instruction::Print {
3558                source: Source::Self_,
3559            },
3560            Instruction::End,
3561        ];
3562
3563        scheduler.spawn(program);
3564        run_to_idle(&mut scheduler);
3565
3566        let output = scheduler.take_output();
3567        assert_eq!(output.len(), 1);
3568        assert!(output[0].contains("Pid(0)"));
3569    }
3570
3571    // ========== Arithmetic Tests ==========
3572
3573    #[test]
3574    fn test_load_int() {
3575        let mut scheduler = Scheduler::new();
3576
3577        let program = vec![
3578            Instruction::LoadInt {
3579                value: 42,
3580                dest: Register(0),
3581            },
3582            Instruction::End,
3583        ];
3584
3585        scheduler.spawn(program);
3586        run_to_idle(&mut scheduler);
3587
3588        let process = scheduler.processes.get(&Pid(0)).unwrap();
3589        assert_eq!(process.registers[0], Value::Int(42));
3590    }
3591
3592    #[test]
3593    fn test_add() {
3594        let mut scheduler = Scheduler::new();
3595
3596        let program = vec![
3597            Instruction::LoadInt {
3598                value: 10,
3599                dest: Register(0),
3600            },
3601            Instruction::Add {
3602                a: Operand::Reg(Register(0)),
3603                b: Operand::Int(5),
3604                dest: Register(1),
3605            },
3606            Instruction::End,
3607        ];
3608
3609        scheduler.spawn(program);
3610        run_to_idle(&mut scheduler);
3611
3612        let process = scheduler.processes.get(&Pid(0)).unwrap();
3613        assert_eq!(process.registers[1], Value::Int(15));
3614    }
3615
3616    #[test]
3617    fn test_sub() {
3618        let mut scheduler = Scheduler::new();
3619
3620        let program = vec![
3621            Instruction::Sub {
3622                a: Operand::Int(20),
3623                b: Operand::Int(7),
3624                dest: Register(0),
3625            },
3626            Instruction::End,
3627        ];
3628
3629        scheduler.spawn(program);
3630        run_to_idle(&mut scheduler);
3631
3632        let process = scheduler.processes.get(&Pid(0)).unwrap();
3633        assert_eq!(process.registers[0], Value::Int(13));
3634    }
3635
3636    #[test]
3637    fn test_mul() {
3638        let mut scheduler = Scheduler::new();
3639
3640        let program = vec![
3641            Instruction::LoadInt {
3642                value: 6,
3643                dest: Register(0),
3644            },
3645            Instruction::LoadInt {
3646                value: 7,
3647                dest: Register(1),
3648            },
3649            Instruction::Mul {
3650                a: Operand::Reg(Register(0)),
3651                b: Operand::Reg(Register(1)),
3652                dest: Register(2),
3653            },
3654            Instruction::End,
3655        ];
3656
3657        scheduler.spawn(program);
3658        run_to_idle(&mut scheduler);
3659
3660        let process = scheduler.processes.get(&Pid(0)).unwrap();
3661        assert_eq!(process.registers[2], Value::Int(42));
3662    }
3663
3664    #[test]
3665    fn test_div() {
3666        let mut scheduler = Scheduler::new();
3667
3668        let program = vec![
3669            Instruction::Div {
3670                a: Operand::Int(100),
3671                b: Operand::Int(7),
3672                dest: Register(0),
3673            },
3674            Instruction::End,
3675        ];
3676
3677        scheduler.spawn(program);
3678        run_to_idle(&mut scheduler);
3679
3680        let process = scheduler.processes.get(&Pid(0)).unwrap();
3681        assert_eq!(process.registers[0], Value::Int(14)); // Integer division
3682    }
3683
3684    #[test]
3685    fn test_div_by_zero_crashes() {
3686        let mut scheduler = Scheduler::new();
3687
3688        let program = vec![
3689            Instruction::Div {
3690                a: Operand::Int(10),
3691                b: Operand::Int(0),
3692                dest: Register(0),
3693            },
3694            Instruction::End,
3695        ];
3696
3697        scheduler.spawn(program);
3698        run_to_idle(&mut scheduler);
3699
3700        let (_, _, _, crashed) = scheduler.process_count();
3701        assert_eq!(crashed, 1);
3702    }
3703
3704    #[test]
3705    fn test_mod() {
3706        let mut scheduler = Scheduler::new();
3707
3708        let program = vec![
3709            Instruction::Mod {
3710                a: Operand::Int(17),
3711                b: Operand::Int(5),
3712                dest: Register(0),
3713            },
3714            Instruction::End,
3715        ];
3716
3717        scheduler.spawn(program);
3718        run_to_idle(&mut scheduler);
3719
3720        let process = scheduler.processes.get(&Pid(0)).unwrap();
3721        assert_eq!(process.registers[0], Value::Int(2));
3722    }
3723
3724    // ========== Comparison Tests ==========
3725
3726    #[test]
3727    fn test_eq() {
3728        let mut scheduler = Scheduler::new();
3729
3730        let program = vec![
3731            Instruction::Eq {
3732                a: Operand::Int(5),
3733                b: Operand::Int(5),
3734                dest: Register(0),
3735            },
3736            Instruction::Eq {
3737                a: Operand::Int(5),
3738                b: Operand::Int(3),
3739                dest: Register(1),
3740            },
3741            Instruction::End,
3742        ];
3743
3744        scheduler.spawn(program);
3745        run_to_idle(&mut scheduler);
3746
3747        let process = scheduler.processes.get(&Pid(0)).unwrap();
3748        assert_eq!(process.registers[0], Value::Int(1)); // true
3749        assert_eq!(process.registers[1], Value::Int(0)); // false
3750    }
3751
3752    #[test]
3753    fn test_lt_gt() {
3754        let mut scheduler = Scheduler::new();
3755
3756        let program = vec![
3757            Instruction::Lt {
3758                a: Operand::Int(3),
3759                b: Operand::Int(5),
3760                dest: Register(0),
3761            },
3762            Instruction::Gt {
3763                a: Operand::Int(3),
3764                b: Operand::Int(5),
3765                dest: Register(1),
3766            },
3767            Instruction::Lt {
3768                a: Operand::Int(5),
3769                b: Operand::Int(3),
3770                dest: Register(2),
3771            },
3772            Instruction::End,
3773        ];
3774
3775        scheduler.spawn(program);
3776        run_to_idle(&mut scheduler);
3777
3778        let process = scheduler.processes.get(&Pid(0)).unwrap();
3779        assert_eq!(process.registers[0], Value::Int(1)); // 3 < 5 = true
3780        assert_eq!(process.registers[1], Value::Int(0)); // 3 > 5 = false
3781        assert_eq!(process.registers[2], Value::Int(0)); // 5 < 3 = false
3782    }
3783
3784    #[test]
3785    fn test_lte_gte() {
3786        let mut scheduler = Scheduler::new();
3787
3788        let program = vec![
3789            Instruction::Lte {
3790                a: Operand::Int(5),
3791                b: Operand::Int(5),
3792                dest: Register(0),
3793            },
3794            Instruction::Gte {
3795                a: Operand::Int(5),
3796                b: Operand::Int(5),
3797                dest: Register(1),
3798            },
3799            Instruction::Lte {
3800                a: Operand::Int(3),
3801                b: Operand::Int(5),
3802                dest: Register(2),
3803            },
3804            Instruction::Gte {
3805                a: Operand::Int(3),
3806                b: Operand::Int(5),
3807                dest: Register(3),
3808            },
3809            Instruction::End,
3810        ];
3811
3812        scheduler.spawn(program);
3813        run_to_idle(&mut scheduler);
3814
3815        let process = scheduler.processes.get(&Pid(0)).unwrap();
3816        assert_eq!(process.registers[0], Value::Int(1)); // 5 <= 5 = true
3817        assert_eq!(process.registers[1], Value::Int(1)); // 5 >= 5 = true
3818        assert_eq!(process.registers[2], Value::Int(1)); // 3 <= 5 = true
3819        assert_eq!(process.registers[3], Value::Int(0)); // 3 >= 5 = false
3820    }
3821
3822    #[test]
3823    fn test_ne() {
3824        let mut scheduler = Scheduler::new();
3825
3826        let program = vec![
3827            Instruction::Ne {
3828                a: Operand::Int(5),
3829                b: Operand::Int(3),
3830                dest: Register(0),
3831            },
3832            Instruction::Ne {
3833                a: Operand::Int(5),
3834                b: Operand::Int(5),
3835                dest: Register(1),
3836            },
3837            Instruction::End,
3838        ];
3839
3840        scheduler.spawn(program);
3841        run_to_idle(&mut scheduler);
3842
3843        let process = scheduler.processes.get(&Pid(0)).unwrap();
3844        assert_eq!(process.registers[0], Value::Int(1)); // 5 != 3 = true
3845        assert_eq!(process.registers[1], Value::Int(0)); // 5 != 5 = false
3846    }
3847
3848    #[test]
3849    fn test_arithmetic_chain() {
3850        let mut scheduler = Scheduler::new();
3851
3852        // Calculate (10 + 5) * 2 - 3 = 27
3853        let program = vec![
3854            Instruction::Add {
3855                a: Operand::Int(10),
3856                b: Operand::Int(5),
3857                dest: Register(0),
3858            },
3859            Instruction::Mul {
3860                a: Operand::Reg(Register(0)),
3861                b: Operand::Int(2),
3862                dest: Register(0),
3863            },
3864            Instruction::Sub {
3865                a: Operand::Reg(Register(0)),
3866                b: Operand::Int(3),
3867                dest: Register(0),
3868            },
3869            Instruction::End,
3870        ];
3871
3872        scheduler.spawn(program);
3873        run_to_idle(&mut scheduler);
3874
3875        let process = scheduler.processes.get(&Pid(0)).unwrap();
3876        assert_eq!(process.registers[0], Value::Int(27));
3877    }
3878
3879    // ========== Control Flow Tests ==========
3880
3881    #[test]
3882    fn test_jump() {
3883        let mut scheduler = Scheduler::new();
3884
3885        // Jump over the first LoadInt, should only execute second one
3886        let program = vec![
3887            // 0: Jump to instruction 2
3888            Instruction::Jump { target: 2 },
3889            // 1: This should be skipped
3890            Instruction::LoadInt {
3891                value: 999,
3892                dest: Register(0),
3893            },
3894            // 2: This should execute
3895            Instruction::LoadInt {
3896                value: 42,
3897                dest: Register(0),
3898            },
3899            Instruction::End,
3900        ];
3901
3902        scheduler.spawn(program);
3903        run_to_idle(&mut scheduler);
3904
3905        let process = scheduler.processes.get(&Pid(0)).unwrap();
3906        assert_eq!(process.registers[0], Value::Int(42));
3907    }
3908
3909    #[test]
3910    fn test_jump_if_truthy() {
3911        let mut scheduler = Scheduler::new();
3912
3913        // Condition is 1 (truthy), should jump
3914        let program = vec![
3915            // 0: Load 1 (truthy)
3916            Instruction::LoadInt {
3917                value: 1,
3918                dest: Register(0),
3919            },
3920            // 1: Jump if truthy to 3
3921            Instruction::JumpIf {
3922                cond: Operand::Reg(Register(0)),
3923                target: 3,
3924            },
3925            // 2: Should be skipped
3926            Instruction::LoadInt {
3927                value: 999,
3928                dest: Register(1),
3929            },
3930            // 3: Should execute
3931            Instruction::LoadInt {
3932                value: 42,
3933                dest: Register(1),
3934            },
3935            Instruction::End,
3936        ];
3937
3938        scheduler.spawn(program);
3939        run_to_idle(&mut scheduler);
3940
3941        let process = scheduler.processes.get(&Pid(0)).unwrap();
3942        assert_eq!(process.registers[1], Value::Int(42));
3943    }
3944
3945    #[test]
3946    fn test_jump_if_falsy() {
3947        let mut scheduler = Scheduler::new();
3948
3949        // Condition is 0 (falsy), should NOT jump
3950        let program = vec![
3951            // 0: Load 0 (falsy)
3952            Instruction::LoadInt {
3953                value: 0,
3954                dest: Register(0),
3955            },
3956            // 1: Jump if truthy - should NOT jump since 0 is falsy
3957            Instruction::JumpIf {
3958                cond: Operand::Reg(Register(0)),
3959                target: 3,
3960            },
3961            // 2: Should execute (not skipped)
3962            Instruction::LoadInt {
3963                value: 42,
3964                dest: Register(1),
3965            },
3966            // 3:
3967            Instruction::End,
3968        ];
3969
3970        scheduler.spawn(program);
3971        run_to_idle(&mut scheduler);
3972
3973        let process = scheduler.processes.get(&Pid(0)).unwrap();
3974        assert_eq!(process.registers[1], Value::Int(42));
3975    }
3976
3977    #[test]
3978    fn test_jump_unless_falsy() {
3979        let mut scheduler = Scheduler::new();
3980
3981        // Condition is 0 (falsy), should jump
3982        let program = vec![
3983            // 0: Load 0 (falsy)
3984            Instruction::LoadInt {
3985                value: 0,
3986                dest: Register(0),
3987            },
3988            // 1: Jump unless truthy - should jump since 0 is falsy
3989            Instruction::JumpUnless {
3990                cond: Operand::Reg(Register(0)),
3991                target: 3,
3992            },
3993            // 2: Should be skipped
3994            Instruction::LoadInt {
3995                value: 999,
3996                dest: Register(1),
3997            },
3998            // 3: Should execute
3999            Instruction::LoadInt {
4000                value: 42,
4001                dest: Register(1),
4002            },
4003            Instruction::End,
4004        ];
4005
4006        scheduler.spawn(program);
4007        run_to_idle(&mut scheduler);
4008
4009        let process = scheduler.processes.get(&Pid(0)).unwrap();
4010        assert_eq!(process.registers[1], Value::Int(42));
4011    }
4012
4013    #[test]
4014    fn test_simple_loop() {
4015        let mut scheduler = Scheduler::new();
4016
4017        // Count down from 5 to 0
4018        // R0 = counter, R1 = accumulator (sum)
4019        let program = vec![
4020            // 0: R0 = 5
4021            Instruction::LoadInt {
4022                value: 5,
4023                dest: Register(0),
4024            },
4025            // 1: R1 = 0
4026            Instruction::LoadInt {
4027                value: 0,
4028                dest: Register(1),
4029            },
4030            // 2: Loop start - R1 = R1 + R0
4031            Instruction::Add {
4032                a: Operand::Reg(Register(1)),
4033                b: Operand::Reg(Register(0)),
4034                dest: Register(1),
4035            },
4036            // 3: R0 = R0 - 1
4037            Instruction::Sub {
4038                a: Operand::Reg(Register(0)),
4039                b: Operand::Int(1),
4040                dest: Register(0),
4041            },
4042            // 4: Jump back to 2 if R0 > 0
4043            Instruction::JumpIf {
4044                cond: Operand::Reg(Register(0)),
4045                target: 2,
4046            },
4047            // 5: End
4048            Instruction::End,
4049        ];
4050
4051        scheduler.spawn(program);
4052        run_to_idle(&mut scheduler);
4053
4054        let process = scheduler.processes.get(&Pid(0)).unwrap();
4055        // Sum of 5+4+3+2+1 = 15
4056        assert_eq!(process.registers[1], Value::Int(15));
4057    }
4058
4059    #[test]
4060    fn test_conditional_with_comparison() {
4061        let mut scheduler = Scheduler::new();
4062
4063        // If 10 > 5, set R1 = 1, else R1 = 0
4064        let program = vec![
4065            // 0: Compare 10 > 5 -> R0
4066            Instruction::Gt {
4067                a: Operand::Int(10),
4068                b: Operand::Int(5),
4069                dest: Register(0),
4070            },
4071            // 1: If R0 is truthy, jump to 3
4072            Instruction::JumpIf {
4073                cond: Operand::Reg(Register(0)),
4074                target: 3,
4075            },
4076            // 2: Else branch - set R1 = 0, jump to end
4077            Instruction::LoadInt {
4078                value: 0,
4079                dest: Register(1),
4080            },
4081            Instruction::Jump { target: 4 },
4082            // 3: Then branch - set R1 = 1
4083            Instruction::LoadInt {
4084                value: 1,
4085                dest: Register(1),
4086            },
4087            // 4: End
4088            Instruction::End,
4089        ];
4090
4091        scheduler.spawn(program);
4092        run_to_idle(&mut scheduler);
4093
4094        let process = scheduler.processes.get(&Pid(0)).unwrap();
4095        assert_eq!(process.registers[1], Value::Int(1)); // 10 > 5, so then branch
4096    }
4097
4098    // ========== Function Call Tests ==========
4099
4100    #[test]
4101    fn test_simple_call_return() {
4102        let mut scheduler = Scheduler::new();
4103
4104        // Main calls a function that sets R0 = 42, then returns
4105        let program = vec![
4106            // 0: Call function at 3
4107            Instruction::Call { target: 3 },
4108            // 1: After return, end
4109            Instruction::End,
4110            // 2: (unreachable)
4111            Instruction::LoadInt {
4112                value: 999,
4113                dest: Register(0),
4114            },
4115            // 3: Function start - set R0 = 42
4116            Instruction::LoadInt {
4117                value: 42,
4118                dest: Register(0),
4119            },
4120            // 4: Return to caller
4121            Instruction::Return,
4122        ];
4123
4124        scheduler.spawn(program);
4125        run_to_idle(&mut scheduler);
4126
4127        let process = scheduler.processes.get(&Pid(0)).unwrap();
4128        assert_eq!(process.registers[0], Value::Int(42));
4129    }
4130
4131    #[test]
4132    fn test_nested_calls() {
4133        let mut scheduler = Scheduler::new();
4134
4135        // Main calls func_a, which calls func_b, which sets R0 = 100
4136        let program = vec![
4137            // 0: Call func_a at 3
4138            Instruction::Call { target: 3 },
4139            // 1: After return, end
4140            Instruction::End,
4141            // 2: (padding)
4142            Instruction::End,
4143            // 3: func_a - call func_b at 6
4144            Instruction::Call { target: 6 },
4145            // 4: Add 10 to R0
4146            Instruction::Add {
4147                a: Operand::Reg(Register(0)),
4148                b: Operand::Int(10),
4149                dest: Register(0),
4150            },
4151            // 5: Return from func_a
4152            Instruction::Return,
4153            // 6: func_b - set R0 = 100
4154            Instruction::LoadInt {
4155                value: 100,
4156                dest: Register(0),
4157            },
4158            // 7: Return from func_b
4159            Instruction::Return,
4160        ];
4161
4162        scheduler.spawn(program);
4163        run_to_idle(&mut scheduler);
4164
4165        let process = scheduler.processes.get(&Pid(0)).unwrap();
4166        // func_b sets 100, func_a adds 10 = 110
4167        assert_eq!(process.registers[0], Value::Int(110));
4168    }
4169
4170    #[test]
4171    fn test_function_with_loop() {
4172        let mut scheduler = Scheduler::new();
4173
4174        // Main sets R0 = 5, calls factorial function which computes iteratively
4175        // factorial(n): R1 = 1, while n > 0: R1 *= n, n--; return R1 in R0
4176        let program = vec![
4177            // 0: Load 5 into R0 (argument)
4178            Instruction::LoadInt {
4179                value: 5,
4180                dest: Register(0),
4181            },
4182            // 1: Call factorial at 4
4183            Instruction::Call { target: 4 },
4184            // 2: End (R0 has result)
4185            Instruction::End,
4186            // 3: (padding)
4187            Instruction::End,
4188            // === factorial function at 4 ===
4189            // R0 = n (input), R1 = accumulator (result)
4190            // 4: R1 = 1 (accumulator)
4191            Instruction::LoadInt {
4192                value: 1,
4193                dest: Register(1),
4194            },
4195            // 5: Loop start - check if R0 <= 0
4196            Instruction::Lte {
4197                a: Operand::Reg(Register(0)),
4198                b: Operand::Int(0),
4199                dest: Register(2),
4200            },
4201            // 6: If R0 <= 0, jump to return
4202            Instruction::JumpIf {
4203                cond: Operand::Reg(Register(2)),
4204                target: 10,
4205            },
4206            // 7: R1 = R1 * R0
4207            Instruction::Mul {
4208                a: Operand::Reg(Register(1)),
4209                b: Operand::Reg(Register(0)),
4210                dest: Register(1),
4211            },
4212            // 8: R0 = R0 - 1
4213            Instruction::Sub {
4214                a: Operand::Reg(Register(0)),
4215                b: Operand::Int(1),
4216                dest: Register(0),
4217            },
4218            // 9: Jump back to loop start
4219            Instruction::Jump { target: 5 },
4220            // 10: Move result to R0 and return
4221            Instruction::Add {
4222                a: Operand::Reg(Register(1)),
4223                b: Operand::Int(0),
4224                dest: Register(0),
4225            },
4226            // 11: Return
4227            Instruction::Return,
4228        ];
4229
4230        scheduler.spawn(program);
4231        run_to_idle(&mut scheduler);
4232
4233        let process = scheduler.processes.get(&Pid(0)).unwrap();
4234        assert_eq!(process.registers[0], Value::Int(120)); // 5! = 120
4235    }
4236
4237    #[test]
4238    fn test_return_without_call_ends_process() {
4239        let mut scheduler = Scheduler::new();
4240
4241        // Return without a call should end the process
4242        let program = vec![
4243            Instruction::LoadInt {
4244                value: 42,
4245                dest: Register(0),
4246            },
4247            Instruction::Return,
4248            // These should never execute
4249            Instruction::LoadInt {
4250                value: 999,
4251                dest: Register(0),
4252            },
4253            Instruction::End,
4254        ];
4255
4256        scheduler.spawn(program);
4257        run_to_idle(&mut scheduler);
4258
4259        let process = scheduler.processes.get(&Pid(0)).unwrap();
4260        assert_eq!(process.registers[0], Value::Int(42));
4261        assert_eq!(process.status, ProcessStatus::Done);
4262    }
4263
4264    // ========== Stack Operation Tests ==========
4265
4266    #[test]
4267    fn test_push_pop() {
4268        let mut scheduler = Scheduler::new();
4269
4270        let program = vec![
4271            // Push some values
4272            Instruction::Push {
4273                source: Operand::Int(10),
4274            },
4275            Instruction::Push {
4276                source: Operand::Int(20),
4277            },
4278            Instruction::Push {
4279                source: Operand::Int(30),
4280            },
4281            // Pop in reverse order (LIFO)
4282            Instruction::Pop { dest: Register(0) }, // 30
4283            Instruction::Pop { dest: Register(1) }, // 20
4284            Instruction::Pop { dest: Register(2) }, // 10
4285            Instruction::End,
4286        ];
4287
4288        scheduler.spawn(program);
4289        run_to_idle(&mut scheduler);
4290
4291        let process = scheduler.processes.get(&Pid(0)).unwrap();
4292        assert_eq!(process.registers[0], Value::Int(30));
4293        assert_eq!(process.registers[1], Value::Int(20));
4294        assert_eq!(process.registers[2], Value::Int(10));
4295    }
4296
4297    #[test]
4298    fn test_push_register() {
4299        let mut scheduler = Scheduler::new();
4300
4301        let program = vec![
4302            Instruction::LoadInt {
4303                value: 42,
4304                dest: Register(0),
4305            },
4306            Instruction::Push {
4307                source: Operand::Reg(Register(0)),
4308            },
4309            Instruction::LoadInt {
4310                value: 0,
4311                dest: Register(0),
4312            },
4313            Instruction::Pop { dest: Register(1) },
4314            Instruction::End,
4315        ];
4316
4317        scheduler.spawn(program);
4318        run_to_idle(&mut scheduler);
4319
4320        let process = scheduler.processes.get(&Pid(0)).unwrap();
4321        assert_eq!(process.registers[0], Value::Int(0)); // Overwritten
4322        assert_eq!(process.registers[1], Value::Int(42)); // Restored from stack
4323    }
4324
4325    #[test]
4326    fn test_pop_empty_crashes() {
4327        let mut scheduler = Scheduler::new();
4328
4329        let program = vec![Instruction::Pop { dest: Register(0) }, Instruction::End];
4330
4331        scheduler.spawn(program);
4332        run_to_idle(&mut scheduler);
4333
4334        let (_, _, _, crashed) = scheduler.process_count();
4335        assert_eq!(crashed, 1);
4336    }
4337
4338    #[test]
4339    fn test_recursive_factorial_with_stack() {
4340        let mut scheduler = Scheduler::new();
4341
4342        // Calculate factorial(5) = 120 using proper stack frames
4343        // R0 = argument/result
4344        let program = vec![
4345            // 0: Load 5 into R0
4346            Instruction::LoadInt {
4347                value: 5,
4348                dest: Register(0),
4349            },
4350            // 1: Call factorial at 4
4351            Instruction::Call { target: 4 },
4352            // 2: End (R0 has result)
4353            Instruction::End,
4354            // 3: (padding)
4355            Instruction::End,
4356            // === factorial function at 4 ===
4357            // 4: If R0 <= 1, return R0 (base case)
4358            Instruction::Lte {
4359                a: Operand::Reg(Register(0)),
4360                b: Operand::Int(1),
4361                dest: Register(1),
4362            },
4363            // 5: Jump to return if base case
4364            Instruction::JumpIf {
4365                cond: Operand::Reg(Register(1)),
4366                target: 13,
4367            },
4368            // 6: Save R0 onto stack
4369            Instruction::Push {
4370                source: Operand::Reg(Register(0)),
4371            },
4372            // 7: R0 = R0 - 1
4373            Instruction::Sub {
4374                a: Operand::Reg(Register(0)),
4375                b: Operand::Int(1),
4376                dest: Register(0),
4377            },
4378            // 8: Recursive call
4379            Instruction::Call { target: 4 },
4380            // 9: Pop saved value into R1
4381            Instruction::Pop { dest: Register(1) },
4382            // 10: R0 = R0 * R1
4383            Instruction::Mul {
4384                a: Operand::Reg(Register(0)),
4385                b: Operand::Reg(Register(1)),
4386                dest: Register(0),
4387            },
4388            // 11: Return
4389            Instruction::Return,
4390            // 12: (padding)
4391            Instruction::End,
4392            // 13: Base case - R0 is already 1, just return
4393            Instruction::Return,
4394        ];
4395
4396        scheduler.spawn(program);
4397        run_to_idle(&mut scheduler);
4398
4399        let process = scheduler.processes.get(&Pid(0)).unwrap();
4400        assert_eq!(process.registers[0], Value::Int(120)); // 5! = 120
4401    }
4402
4403    #[test]
4404    fn test_recursive_fibonacci() {
4405        let mut scheduler = Scheduler::new();
4406
4407        // Calculate fibonacci(10) = 55
4408        // R0 = argument/result
4409        let program = vec![
4410            // 0: Load 10 into R0
4411            Instruction::LoadInt {
4412                value: 10,
4413                dest: Register(0),
4414            },
4415            // 1: Call fib at 4
4416            Instruction::Call { target: 4 },
4417            // 2: End
4418            Instruction::End,
4419            // 3: (padding)
4420            Instruction::End,
4421            // === fib function at 4 ===
4422            // 4: If R0 <= 1, return R0
4423            Instruction::Lte {
4424                a: Operand::Reg(Register(0)),
4425                b: Operand::Int(1),
4426                dest: Register(1),
4427            },
4428            // 5: Jump to return if base case
4429            Instruction::JumpIf {
4430                cond: Operand::Reg(Register(1)),
4431                target: 16,
4432            },
4433            // 6: Save n onto stack
4434            Instruction::Push {
4435                source: Operand::Reg(Register(0)),
4436            },
4437            // 7: R0 = n - 1
4438            Instruction::Sub {
4439                a: Operand::Reg(Register(0)),
4440                b: Operand::Int(1),
4441                dest: Register(0),
4442            },
4443            // 8: Call fib(n-1)
4444            Instruction::Call { target: 4 },
4445            // 9: Move fib(n-1) to R2
4446            Instruction::Add {
4447                a: Operand::Reg(Register(0)),
4448                b: Operand::Int(0),
4449                dest: Register(2),
4450            },
4451            // 10: Pop original n into R0
4452            Instruction::Pop { dest: Register(0) },
4453            // 11: Push fib(n-1) for later
4454            Instruction::Push {
4455                source: Operand::Reg(Register(2)),
4456            },
4457            // 12: R0 = n - 2
4458            Instruction::Sub {
4459                a: Operand::Reg(Register(0)),
4460                b: Operand::Int(2),
4461                dest: Register(0),
4462            },
4463            // 13: Call fib(n-2)
4464            Instruction::Call { target: 4 },
4465            // 14: Pop fib(n-1) into R1
4466            Instruction::Pop { dest: Register(1) },
4467            // 15: R0 = fib(n-1) + fib(n-2)
4468            Instruction::Add {
4469                a: Operand::Reg(Register(0)),
4470                b: Operand::Reg(Register(1)),
4471                dest: Register(0),
4472            },
4473            // 16: Return
4474            Instruction::Return,
4475        ];
4476
4477        scheduler.spawn(program);
4478        run_to_idle(&mut scheduler);
4479
4480        let process = scheduler.processes.get(&Pid(0)).unwrap();
4481        assert_eq!(process.registers[0], Value::Int(55)); // fib(10) = 55
4482    }
4483
4484    // ========== Atom & Tuple Tests ==========
4485
4486    #[test]
4487    fn test_load_atom() {
4488        let mut scheduler = Scheduler::new();
4489
4490        let program = vec![
4491            Instruction::LoadAtom {
4492                name: "ok".to_string(),
4493                dest: Register(0),
4494            },
4495            Instruction::LoadAtom {
4496                name: "error".to_string(),
4497                dest: Register(1),
4498            },
4499            Instruction::End,
4500        ];
4501
4502        scheduler.spawn(program);
4503        run_to_idle(&mut scheduler);
4504
4505        let process = scheduler.processes.get(&Pid(0)).unwrap();
4506        assert_eq!(process.registers[0], Value::Atom("ok".to_string()));
4507        assert_eq!(process.registers[1], Value::Atom("error".to_string()));
4508    }
4509
4510    #[test]
4511    fn test_make_tuple() {
4512        let mut scheduler = Scheduler::new();
4513
4514        // Create tuple {:ok, 42}
4515        let program = vec![
4516            Instruction::LoadAtom {
4517                name: "ok".to_string(),
4518                dest: Register(0),
4519            },
4520            Instruction::LoadInt {
4521                value: 42,
4522                dest: Register(1),
4523            },
4524            // Push elements onto stack
4525            Instruction::Push {
4526                source: Operand::Reg(Register(0)),
4527            },
4528            Instruction::Push {
4529                source: Operand::Reg(Register(1)),
4530            },
4531            // Make 2-tuple
4532            Instruction::MakeTuple {
4533                arity: 2,
4534                dest: Register(2),
4535            },
4536            Instruction::End,
4537        ];
4538
4539        scheduler.spawn(program);
4540        run_to_idle(&mut scheduler);
4541
4542        let process = scheduler.processes.get(&Pid(0)).unwrap();
4543        assert_eq!(
4544            process.registers[2],
4545            Value::Tuple(vec![Value::Atom("ok".to_string()), Value::Int(42),])
4546        );
4547    }
4548
4549    #[test]
4550    fn test_tuple_element() {
4551        let mut scheduler = Scheduler::new();
4552
4553        // Create tuple {:error, "not found", 404} and extract elements
4554        let program = vec![
4555            Instruction::LoadAtom {
4556                name: "error".to_string(),
4557                dest: Register(0),
4558            },
4559            Instruction::LoadInt {
4560                value: 404,
4561                dest: Register(1),
4562            },
4563            Instruction::Push {
4564                source: Operand::Reg(Register(0)),
4565            },
4566            Instruction::Push {
4567                source: Operand::Reg(Register(1)),
4568            },
4569            Instruction::MakeTuple {
4570                arity: 2,
4571                dest: Register(0),
4572            },
4573            // Extract element 0 (the atom)
4574            Instruction::TupleElement {
4575                tuple: Register(0),
4576                index: 0,
4577                dest: Register(1),
4578            },
4579            // Extract element 1 (the integer)
4580            Instruction::TupleElement {
4581                tuple: Register(0),
4582                index: 1,
4583                dest: Register(2),
4584            },
4585            Instruction::End,
4586        ];
4587
4588        scheduler.spawn(program);
4589        run_to_idle(&mut scheduler);
4590
4591        let process = scheduler.processes.get(&Pid(0)).unwrap();
4592        assert_eq!(process.registers[1], Value::Atom("error".to_string()));
4593        assert_eq!(process.registers[2], Value::Int(404));
4594    }
4595
4596    #[test]
4597    fn test_tuple_arity() {
4598        let mut scheduler = Scheduler::new();
4599
4600        let program = vec![
4601            // Create a 3-tuple
4602            Instruction::Push {
4603                source: Operand::Int(1),
4604            },
4605            Instruction::Push {
4606                source: Operand::Int(2),
4607            },
4608            Instruction::Push {
4609                source: Operand::Int(3),
4610            },
4611            Instruction::MakeTuple {
4612                arity: 3,
4613                dest: Register(0),
4614            },
4615            Instruction::TupleArity {
4616                tuple: Register(0),
4617                dest: Register(1),
4618            },
4619            Instruction::End,
4620        ];
4621
4622        scheduler.spawn(program);
4623        run_to_idle(&mut scheduler);
4624
4625        let process = scheduler.processes.get(&Pid(0)).unwrap();
4626        assert_eq!(process.registers[1], Value::Int(3));
4627    }
4628
4629    #[test]
4630    fn test_tuple_element_out_of_bounds_crashes() {
4631        let mut scheduler = Scheduler::new();
4632
4633        let program = vec![
4634            Instruction::Push {
4635                source: Operand::Int(1),
4636            },
4637            Instruction::MakeTuple {
4638                arity: 1,
4639                dest: Register(0),
4640            },
4641            // Try to access index 5 (out of bounds)
4642            Instruction::TupleElement {
4643                tuple: Register(0),
4644                index: 5,
4645                dest: Register(1),
4646            },
4647            Instruction::End,
4648        ];
4649
4650        scheduler.spawn(program);
4651        run_to_idle(&mut scheduler);
4652
4653        let (_, _, _, crashed) = scheduler.process_count();
4654        assert_eq!(crashed, 1);
4655    }
4656
4657    #[test]
4658    fn test_nested_tuples() {
4659        let mut scheduler = Scheduler::new();
4660
4661        // Create {{:inner, 1}, :outer}
4662        let program = vec![
4663            // Create inner tuple {:inner, 1}
4664            Instruction::LoadAtom {
4665                name: "inner".to_string(),
4666                dest: Register(0),
4667            },
4668            Instruction::Push {
4669                source: Operand::Reg(Register(0)),
4670            },
4671            Instruction::Push {
4672                source: Operand::Int(1),
4673            },
4674            Instruction::MakeTuple {
4675                arity: 2,
4676                dest: Register(0),
4677            },
4678            // Create outer tuple {inner_tuple, :outer}
4679            Instruction::LoadAtom {
4680                name: "outer".to_string(),
4681                dest: Register(1),
4682            },
4683            Instruction::Push {
4684                source: Operand::Reg(Register(0)),
4685            },
4686            Instruction::Push {
4687                source: Operand::Reg(Register(1)),
4688            },
4689            Instruction::MakeTuple {
4690                arity: 2,
4691                dest: Register(2),
4692            },
4693            // Extract inner tuple
4694            Instruction::TupleElement {
4695                tuple: Register(2),
4696                index: 0,
4697                dest: Register(3),
4698            },
4699            // Extract element from inner tuple
4700            Instruction::TupleElement {
4701                tuple: Register(3),
4702                index: 1,
4703                dest: Register(4),
4704            },
4705            Instruction::End,
4706        ];
4707
4708        scheduler.spawn(program);
4709        run_to_idle(&mut scheduler);
4710
4711        let process = scheduler.processes.get(&Pid(0)).unwrap();
4712        // R4 should have the integer 1 from the inner tuple
4713        assert_eq!(process.registers[4], Value::Int(1));
4714    }
4715
4716    // ========== Pattern Matching Tests ==========
4717
4718    #[test]
4719    fn test_match_wildcard() {
4720        let mut scheduler = Scheduler::new();
4721
4722        let program = vec![
4723            Instruction::LoadInt {
4724                value: 42,
4725                dest: Register(0),
4726            },
4727            // Match anything with wildcard - should succeed
4728            Instruction::Match {
4729                source: Register(0),
4730                pattern: Pattern::Wildcard,
4731                fail_target: 4,
4732            },
4733            // Success path: set R1 = 1
4734            Instruction::LoadInt {
4735                value: 1,
4736                dest: Register(1),
4737            },
4738            Instruction::End,
4739            // Fail path: set R1 = 0
4740            Instruction::LoadInt {
4741                value: 0,
4742                dest: Register(1),
4743            },
4744            Instruction::End,
4745        ];
4746
4747        scheduler.spawn(program);
4748        run_to_idle(&mut scheduler);
4749
4750        let process = scheduler.processes.get(&Pid(0)).unwrap();
4751        assert_eq!(process.registers[1], Value::Int(1)); // Success path taken
4752    }
4753
4754    #[test]
4755    fn test_match_variable() {
4756        let mut scheduler = Scheduler::new();
4757
4758        let program = vec![
4759            Instruction::LoadInt {
4760                value: 42,
4761                dest: Register(0),
4762            },
4763            // Match and bind to R1
4764            Instruction::Match {
4765                source: Register(0),
4766                pattern: Pattern::Variable(Register(1)),
4767                fail_target: 3,
4768            },
4769            Instruction::End,
4770            // Fail path
4771            Instruction::LoadInt {
4772                value: 0,
4773                dest: Register(1),
4774            },
4775            Instruction::End,
4776        ];
4777
4778        scheduler.spawn(program);
4779        run_to_idle(&mut scheduler);
4780
4781        let process = scheduler.processes.get(&Pid(0)).unwrap();
4782        assert_eq!(process.registers[1], Value::Int(42)); // Bound value
4783    }
4784
4785    #[test]
4786    fn test_match_int_success() {
4787        let mut scheduler = Scheduler::new();
4788
4789        let program = vec![
4790            Instruction::LoadInt {
4791                value: 42,
4792                dest: Register(0),
4793            },
4794            // Match exact integer
4795            Instruction::Match {
4796                source: Register(0),
4797                pattern: Pattern::Int(42),
4798                fail_target: 4,
4799            },
4800            Instruction::LoadInt {
4801                value: 1,
4802                dest: Register(1),
4803            },
4804            Instruction::End,
4805            // Fail
4806            Instruction::LoadInt {
4807                value: 0,
4808                dest: Register(1),
4809            },
4810            Instruction::End,
4811        ];
4812
4813        scheduler.spawn(program);
4814        run_to_idle(&mut scheduler);
4815
4816        let process = scheduler.processes.get(&Pid(0)).unwrap();
4817        assert_eq!(process.registers[1], Value::Int(1)); // Success
4818    }
4819
4820    #[test]
4821    fn test_match_int_failure() {
4822        let mut scheduler = Scheduler::new();
4823
4824        let program = vec![
4825            Instruction::LoadInt {
4826                value: 42,
4827                dest: Register(0),
4828            },
4829            // Match wrong integer - should fail
4830            Instruction::Match {
4831                source: Register(0),
4832                pattern: Pattern::Int(99),
4833                fail_target: 4,
4834            },
4835            Instruction::LoadInt {
4836                value: 1,
4837                dest: Register(1),
4838            },
4839            Instruction::End,
4840            // Fail path
4841            Instruction::LoadInt {
4842                value: 0,
4843                dest: Register(1),
4844            },
4845            Instruction::End,
4846        ];
4847
4848        scheduler.spawn(program);
4849        run_to_idle(&mut scheduler);
4850
4851        let process = scheduler.processes.get(&Pid(0)).unwrap();
4852        assert_eq!(process.registers[1], Value::Int(0)); // Fail path taken
4853    }
4854
4855    #[test]
4856    fn test_match_atom() {
4857        let mut scheduler = Scheduler::new();
4858
4859        let program = vec![
4860            Instruction::LoadAtom {
4861                name: "ok".to_string(),
4862                dest: Register(0),
4863            },
4864            // Match :ok
4865            Instruction::Match {
4866                source: Register(0),
4867                pattern: Pattern::Atom("ok".to_string()),
4868                fail_target: 4,
4869            },
4870            Instruction::LoadInt {
4871                value: 1,
4872                dest: Register(1),
4873            },
4874            Instruction::End,
4875            // Fail
4876            Instruction::LoadInt {
4877                value: 0,
4878                dest: Register(1),
4879            },
4880            Instruction::End,
4881        ];
4882
4883        scheduler.spawn(program);
4884        run_to_idle(&mut scheduler);
4885
4886        let process = scheduler.processes.get(&Pid(0)).unwrap();
4887        assert_eq!(process.registers[1], Value::Int(1)); // Success
4888    }
4889
4890    #[test]
4891    fn test_match_tuple_with_bindings() {
4892        let mut scheduler = Scheduler::new();
4893
4894        // Create {:ok, 42} and match with {:ok, x}
4895        let program = vec![
4896            // Build tuple {:ok, 42}
4897            Instruction::LoadAtom {
4898                name: "ok".to_string(),
4899                dest: Register(0),
4900            },
4901            Instruction::Push {
4902                source: Operand::Reg(Register(0)),
4903            },
4904            Instruction::Push {
4905                source: Operand::Int(42),
4906            },
4907            Instruction::MakeTuple {
4908                arity: 2,
4909                dest: Register(0),
4910            },
4911            // Match {:ok, x} binding x to R1
4912            Instruction::Match {
4913                source: Register(0),
4914                pattern: Pattern::Tuple(vec![
4915                    Pattern::Atom("ok".to_string()),
4916                    Pattern::Variable(Register(1)),
4917                ]),
4918                fail_target: 7,
4919            },
4920            // Success: R2 = 1
4921            Instruction::LoadInt {
4922                value: 1,
4923                dest: Register(2),
4924            },
4925            Instruction::End,
4926            // Fail: R2 = 0
4927            Instruction::LoadInt {
4928                value: 0,
4929                dest: Register(2),
4930            },
4931            Instruction::End,
4932        ];
4933
4934        scheduler.spawn(program);
4935        run_to_idle(&mut scheduler);
4936
4937        let process = scheduler.processes.get(&Pid(0)).unwrap();
4938        assert_eq!(process.registers[1], Value::Int(42)); // Bound value
4939        assert_eq!(process.registers[2], Value::Int(1)); // Success path
4940    }
4941
4942    #[test]
4943    fn test_match_tuple_wrong_atom() {
4944        let mut scheduler = Scheduler::new();
4945
4946        // Create {:error, 42} and try to match {:ok, x} - should fail
4947        let program = vec![
4948            Instruction::LoadAtom {
4949                name: "error".to_string(),
4950                dest: Register(0),
4951            },
4952            Instruction::Push {
4953                source: Operand::Reg(Register(0)),
4954            },
4955            Instruction::Push {
4956                source: Operand::Int(42),
4957            },
4958            Instruction::MakeTuple {
4959                arity: 2,
4960                dest: Register(0),
4961            },
4962            // Match {:ok, x} - should fail
4963            Instruction::Match {
4964                source: Register(0),
4965                pattern: Pattern::Tuple(vec![
4966                    Pattern::Atom("ok".to_string()),
4967                    Pattern::Variable(Register(1)),
4968                ]),
4969                fail_target: 7,
4970            },
4971            Instruction::LoadInt {
4972                value: 1,
4973                dest: Register(2),
4974            },
4975            Instruction::End,
4976            // Fail path
4977            Instruction::LoadInt {
4978                value: 0,
4979                dest: Register(2),
4980            },
4981            Instruction::End,
4982        ];
4983
4984        scheduler.spawn(program);
4985        run_to_idle(&mut scheduler);
4986
4987        let process = scheduler.processes.get(&Pid(0)).unwrap();
4988        assert_eq!(process.registers[2], Value::Int(0)); // Fail path taken
4989    }
4990
4991    #[test]
4992    fn test_match_tuple_wrong_arity() {
4993        let mut scheduler = Scheduler::new();
4994
4995        // Create {1, 2, 3} and try to match {a, b} - should fail
4996        let program = vec![
4997            Instruction::Push {
4998                source: Operand::Int(1),
4999            },
5000            Instruction::Push {
5001                source: Operand::Int(2),
5002            },
5003            Instruction::Push {
5004                source: Operand::Int(3),
5005            },
5006            Instruction::MakeTuple {
5007                arity: 3,
5008                dest: Register(0),
5009            },
5010            // Match {a, b} - wrong arity
5011            Instruction::Match {
5012                source: Register(0),
5013                pattern: Pattern::Tuple(vec![
5014                    Pattern::Variable(Register(1)),
5015                    Pattern::Variable(Register(2)),
5016                ]),
5017                fail_target: 7,
5018            },
5019            Instruction::LoadInt {
5020                value: 1,
5021                dest: Register(3),
5022            },
5023            Instruction::End,
5024            // Fail path
5025            Instruction::LoadInt {
5026                value: 0,
5027                dest: Register(3),
5028            },
5029            Instruction::End,
5030        ];
5031
5032        scheduler.spawn(program);
5033        run_to_idle(&mut scheduler);
5034
5035        let process = scheduler.processes.get(&Pid(0)).unwrap();
5036        assert_eq!(process.registers[3], Value::Int(0)); // Fail path taken
5037    }
5038
5039    #[test]
5040    fn test_match_nested_tuple() {
5041        let mut scheduler = Scheduler::new();
5042
5043        // Create {:ok, {:data, 100}} and match {:ok, {:data, x}}
5044        let program = vec![
5045            // Build inner tuple {:data, 100}
5046            Instruction::LoadAtom {
5047                name: "data".to_string(),
5048                dest: Register(0),
5049            },
5050            Instruction::Push {
5051                source: Operand::Reg(Register(0)),
5052            },
5053            Instruction::Push {
5054                source: Operand::Int(100),
5055            },
5056            Instruction::MakeTuple {
5057                arity: 2,
5058                dest: Register(0),
5059            },
5060            // Build outer tuple {:ok, inner}
5061            Instruction::LoadAtom {
5062                name: "ok".to_string(),
5063                dest: Register(1),
5064            },
5065            Instruction::Push {
5066                source: Operand::Reg(Register(1)),
5067            },
5068            Instruction::Push {
5069                source: Operand::Reg(Register(0)),
5070            },
5071            Instruction::MakeTuple {
5072                arity: 2,
5073                dest: Register(0),
5074            },
5075            // Match {:ok, {:data, x}}
5076            Instruction::Match {
5077                source: Register(0),
5078                pattern: Pattern::Tuple(vec![
5079                    Pattern::Atom("ok".to_string()),
5080                    Pattern::Tuple(vec![
5081                        Pattern::Atom("data".to_string()),
5082                        Pattern::Variable(Register(1)),
5083                    ]),
5084                ]),
5085                fail_target: 11,
5086            },
5087            Instruction::LoadInt {
5088                value: 1,
5089                dest: Register(2),
5090            },
5091            Instruction::End,
5092            // Fail
5093            Instruction::LoadInt {
5094                value: 0,
5095                dest: Register(2),
5096            },
5097            Instruction::End,
5098        ];
5099
5100        scheduler.spawn(program);
5101        run_to_idle(&mut scheduler);
5102
5103        let process = scheduler.processes.get(&Pid(0)).unwrap();
5104        assert_eq!(process.registers[1], Value::Int(100)); // Extracted value
5105        assert_eq!(process.registers[2], Value::Int(1)); // Success
5106    }
5107
5108    // ========== ReceiveMatch Tests ==========
5109
5110    #[test]
5111    fn test_receive_match_simple() {
5112        let mut scheduler = Scheduler::new();
5113
5114        // Parent sends "hello", child receives with pattern match
5115        let child_code = vec![
5116            // Receive any message into R0
5117            Instruction::ReceiveMatch {
5118                clauses: vec![(Pattern::Variable(Register(0)), 2)],
5119                timeout: None,
5120                timeout_target: 0,
5121            },
5122            Instruction::End, // unreachable
5123            // Clause target: R1 = 1 to indicate success
5124            Instruction::LoadInt {
5125                value: 1,
5126                dest: Register(1),
5127            },
5128            Instruction::End,
5129        ];
5130
5131        let parent_code = vec![
5132            Instruction::Spawn {
5133                code: child_code,
5134                dest: Register(0),
5135            },
5136            Instruction::Send {
5137                to: Source::Reg(Register(0)),
5138                msg: "hello".to_string(),
5139            },
5140            Instruction::End,
5141        ];
5142
5143        scheduler.spawn(parent_code);
5144        run_to_idle(&mut scheduler);
5145
5146        let child = scheduler.processes.get(&Pid(1)).unwrap();
5147        assert_eq!(child.registers[0], Value::String("hello".to_string()));
5148        assert_eq!(child.registers[1], Value::Int(1));
5149    }
5150
5151    #[test]
5152    fn test_receive_match_specific_string() {
5153        let mut scheduler = Scheduler::new();
5154
5155        // Child waits for "ping" specifically
5156        let child_code = vec![
5157            Instruction::ReceiveMatch {
5158                clauses: vec![(Pattern::String("ping".to_string()), 2)],
5159                timeout: None,
5160                timeout_target: 0,
5161            },
5162            Instruction::End,
5163            // Match success
5164            Instruction::LoadInt {
5165                value: 1,
5166                dest: Register(0),
5167            },
5168            Instruction::End,
5169        ];
5170
5171        let parent_code = vec![
5172            Instruction::Spawn {
5173                code: child_code,
5174                dest: Register(0),
5175            },
5176            // Send "pong" first (won't match)
5177            Instruction::Send {
5178                to: Source::Reg(Register(0)),
5179                msg: "pong".to_string(),
5180            },
5181            // Then send "ping" (will match)
5182            Instruction::Send {
5183                to: Source::Reg(Register(0)),
5184                msg: "ping".to_string(),
5185            },
5186            Instruction::End,
5187        ];
5188
5189        scheduler.spawn(parent_code);
5190        run_to_idle(&mut scheduler);
5191
5192        let child = scheduler.processes.get(&Pid(1)).unwrap();
5193        assert_eq!(child.registers[0], Value::Int(1));
5194        // "pong" should still be in mailbox
5195        assert_eq!(child.mailbox.len(), 1);
5196    }
5197
5198    #[test]
5199    fn test_receive_match_multiple_clauses() {
5200        let mut scheduler = Scheduler::new();
5201
5202        // Child handles "ping" or "pong" differently
5203        let child_code = vec![
5204            Instruction::ReceiveMatch {
5205                clauses: vec![
5206                    (Pattern::String("ping".to_string()), 2), // -> set R0 = 1
5207                    (Pattern::String("pong".to_string()), 5), // -> set R0 = 2
5208                ],
5209                timeout: None,
5210                timeout_target: 0,
5211            },
5212            Instruction::End,
5213            // ping handler
5214            Instruction::LoadInt {
5215                value: 1,
5216                dest: Register(0),
5217            },
5218            Instruction::End,
5219            Instruction::End, // padding
5220            // pong handler
5221            Instruction::LoadInt {
5222                value: 2,
5223                dest: Register(0),
5224            },
5225            Instruction::End,
5226        ];
5227
5228        let parent_code = vec![
5229            Instruction::Spawn {
5230                code: child_code,
5231                dest: Register(0),
5232            },
5233            Instruction::Send {
5234                to: Source::Reg(Register(0)),
5235                msg: "pong".to_string(),
5236            },
5237            Instruction::End,
5238        ];
5239
5240        scheduler.spawn(parent_code);
5241        run_to_idle(&mut scheduler);
5242
5243        let child = scheduler.processes.get(&Pid(1)).unwrap();
5244        assert_eq!(child.registers[0], Value::Int(2)); // pong handler
5245    }
5246
5247    #[test]
5248    fn test_receive_match_timeout() {
5249        let mut scheduler = Scheduler::new();
5250
5251        // Process waits for message with timeout
5252        let program = vec![
5253            Instruction::ReceiveMatch {
5254                clauses: vec![(Pattern::String("hello".to_string()), 3)],
5255                timeout: Some(5),
5256                timeout_target: 5,
5257            },
5258            Instruction::End, // unreachable
5259            Instruction::End, // padding
5260            // Match success (won't happen)
5261            Instruction::LoadInt {
5262                value: 1,
5263                dest: Register(0),
5264            },
5265            Instruction::End,
5266            // Timeout handler
5267            Instruction::LoadInt {
5268                value: 99,
5269                dest: Register(0),
5270            },
5271            Instruction::End,
5272        ];
5273
5274        scheduler.spawn(program);
5275        run_to_idle(&mut scheduler);
5276
5277        let process = scheduler.processes.get(&Pid(0)).unwrap();
5278        assert_eq!(process.registers[0], Value::Int(99)); // Timeout handler ran
5279    }
5280
5281    #[test]
5282    fn test_receive_match_selective() {
5283        let mut scheduler = Scheduler::new();
5284
5285        // Test selective receive: skip non-matching messages
5286        let child_code = vec![
5287            // Wait for "second"
5288            Instruction::ReceiveMatch {
5289                clauses: vec![(Pattern::String("second".to_string()), 2)],
5290                timeout: None,
5291                timeout_target: 0,
5292            },
5293            Instruction::End,
5294            // Got "second"
5295            Instruction::LoadInt {
5296                value: 1,
5297                dest: Register(0),
5298            },
5299            // Now receive "first" which was skipped
5300            Instruction::ReceiveMatch {
5301                clauses: vec![(Pattern::String("first".to_string()), 5)],
5302                timeout: None,
5303                timeout_target: 0,
5304            },
5305            Instruction::End,
5306            // Got "first"
5307            Instruction::LoadInt {
5308                value: 2,
5309                dest: Register(1),
5310            },
5311            Instruction::End,
5312        ];
5313
5314        let parent_code = vec![
5315            Instruction::Spawn {
5316                code: child_code,
5317                dest: Register(0),
5318            },
5319            // Send "first" then "second"
5320            Instruction::Send {
5321                to: Source::Reg(Register(0)),
5322                msg: "first".to_string(),
5323            },
5324            Instruction::Send {
5325                to: Source::Reg(Register(0)),
5326                msg: "second".to_string(),
5327            },
5328            Instruction::End,
5329        ];
5330
5331        scheduler.spawn(parent_code);
5332        run_to_idle(&mut scheduler);
5333
5334        let child = scheduler.processes.get(&Pid(1)).unwrap();
5335        assert_eq!(child.registers[0], Value::Int(1)); // Got "second"
5336        assert_eq!(child.registers[1], Value::Int(2)); // Then got "first"
5337    }
5338
5339    // ========== List Tests ==========
5340
5341    #[test]
5342    fn test_make_list() {
5343        let mut scheduler = Scheduler::new();
5344
5345        let program = vec![
5346            // Push elements onto stack
5347            Instruction::Push {
5348                source: Operand::Int(1),
5349            },
5350            Instruction::Push {
5351                source: Operand::Int(2),
5352            },
5353            Instruction::Push {
5354                source: Operand::Int(3),
5355            },
5356            // Make list [1, 2, 3]
5357            Instruction::MakeList {
5358                length: 3,
5359                dest: Register(0),
5360            },
5361            Instruction::End,
5362        ];
5363
5364        scheduler.spawn(program);
5365        run_to_idle(&mut scheduler);
5366
5367        let process = scheduler.processes.get(&Pid(0)).unwrap();
5368        assert_eq!(
5369            process.registers[0],
5370            Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
5371        );
5372    }
5373
5374    #[test]
5375    fn test_make_empty_list() {
5376        let mut scheduler = Scheduler::new();
5377
5378        let program = vec![
5379            Instruction::MakeList {
5380                length: 0,
5381                dest: Register(0),
5382            },
5383            Instruction::End,
5384        ];
5385
5386        scheduler.spawn(program);
5387        run_to_idle(&mut scheduler);
5388
5389        let process = scheduler.processes.get(&Pid(0)).unwrap();
5390        assert_eq!(process.registers[0], Value::List(vec![]));
5391    }
5392
5393    #[test]
5394    fn test_cons() {
5395        let mut scheduler = Scheduler::new();
5396
5397        let program = vec![
5398            // Create empty list
5399            Instruction::MakeList {
5400                length: 0,
5401                dest: Register(0),
5402            },
5403            // Load 3
5404            Instruction::LoadInt {
5405                value: 3,
5406                dest: Register(1),
5407            },
5408            // Cons 3 onto empty list: [3]
5409            Instruction::Cons {
5410                head: Register(1),
5411                tail: Register(0),
5412                dest: Register(0),
5413            },
5414            // Load 2
5415            Instruction::LoadInt {
5416                value: 2,
5417                dest: Register(1),
5418            },
5419            // Cons 2 onto [3]: [2, 3]
5420            Instruction::Cons {
5421                head: Register(1),
5422                tail: Register(0),
5423                dest: Register(0),
5424            },
5425            // Load 1
5426            Instruction::LoadInt {
5427                value: 1,
5428                dest: Register(1),
5429            },
5430            // Cons 1 onto [2, 3]: [1, 2, 3]
5431            Instruction::Cons {
5432                head: Register(1),
5433                tail: Register(0),
5434                dest: Register(0),
5435            },
5436            Instruction::End,
5437        ];
5438
5439        scheduler.spawn(program);
5440        run_to_idle(&mut scheduler);
5441
5442        let process = scheduler.processes.get(&Pid(0)).unwrap();
5443        assert_eq!(
5444            process.registers[0],
5445            Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
5446        );
5447    }
5448
5449    #[test]
5450    fn test_list_head_tail() {
5451        let mut scheduler = Scheduler::new();
5452
5453        let program = vec![
5454            // Create list [1, 2, 3]
5455            Instruction::Push {
5456                source: Operand::Int(1),
5457            },
5458            Instruction::Push {
5459                source: Operand::Int(2),
5460            },
5461            Instruction::Push {
5462                source: Operand::Int(3),
5463            },
5464            Instruction::MakeList {
5465                length: 3,
5466                dest: Register(0),
5467            },
5468            // Get head -> 1
5469            Instruction::ListHead {
5470                list: Register(0),
5471                dest: Register(1),
5472            },
5473            // Get tail -> [2, 3]
5474            Instruction::ListTail {
5475                list: Register(0),
5476                dest: Register(2),
5477            },
5478            Instruction::End,
5479        ];
5480
5481        scheduler.spawn(program);
5482        run_to_idle(&mut scheduler);
5483
5484        let process = scheduler.processes.get(&Pid(0)).unwrap();
5485        assert_eq!(process.registers[1], Value::Int(1));
5486        assert_eq!(
5487            process.registers[2],
5488            Value::List(vec![Value::Int(2), Value::Int(3)])
5489        );
5490    }
5491
5492    #[test]
5493    fn test_list_is_empty() {
5494        let mut scheduler = Scheduler::new();
5495
5496        let program = vec![
5497            // Create empty list
5498            Instruction::MakeList {
5499                length: 0,
5500                dest: Register(0),
5501            },
5502            Instruction::ListIsEmpty {
5503                list: Register(0),
5504                dest: Register(1),
5505            },
5506            // Create non-empty list
5507            Instruction::Push {
5508                source: Operand::Int(1),
5509            },
5510            Instruction::MakeList {
5511                length: 1,
5512                dest: Register(2),
5513            },
5514            Instruction::ListIsEmpty {
5515                list: Register(2),
5516                dest: Register(3),
5517            },
5518            Instruction::End,
5519        ];
5520
5521        scheduler.spawn(program);
5522        run_to_idle(&mut scheduler);
5523
5524        let process = scheduler.processes.get(&Pid(0)).unwrap();
5525        assert_eq!(process.registers[1], Value::Int(1)); // empty list
5526        assert_eq!(process.registers[3], Value::Int(0)); // non-empty list
5527    }
5528
5529    #[test]
5530    fn test_list_head_empty_crashes() {
5531        let mut scheduler = Scheduler::new();
5532
5533        let program = vec![
5534            Instruction::MakeList {
5535                length: 0,
5536                dest: Register(0),
5537            },
5538            Instruction::ListHead {
5539                list: Register(0),
5540                dest: Register(1),
5541            },
5542            Instruction::End,
5543        ];
5544
5545        scheduler.spawn(program);
5546        run_to_idle(&mut scheduler);
5547
5548        let (_, _, _, crashed) = scheduler.process_count();
5549        assert_eq!(crashed, 1);
5550    }
5551
5552    #[test]
5553    fn test_match_list_empty() {
5554        let mut scheduler = Scheduler::new();
5555
5556        let program = vec![
5557            // Create empty list
5558            Instruction::MakeList {
5559                length: 0,
5560                dest: Register(0),
5561            },
5562            // Match against []
5563            Instruction::Match {
5564                source: Register(0),
5565                pattern: Pattern::ListEmpty,
5566                fail_target: 4,
5567            },
5568            // Success
5569            Instruction::LoadInt {
5570                value: 1,
5571                dest: Register(1),
5572            },
5573            Instruction::End,
5574            // Fail
5575            Instruction::LoadInt {
5576                value: 0,
5577                dest: Register(1),
5578            },
5579            Instruction::End,
5580        ];
5581
5582        scheduler.spawn(program);
5583        run_to_idle(&mut scheduler);
5584
5585        let process = scheduler.processes.get(&Pid(0)).unwrap();
5586        assert_eq!(process.registers[1], Value::Int(1)); // Success
5587    }
5588
5589    #[test]
5590    fn test_match_list_cons() {
5591        let mut scheduler = Scheduler::new();
5592
5593        let program = vec![
5594            // Create list [1, 2, 3]
5595            Instruction::Push {
5596                source: Operand::Int(1),
5597            },
5598            Instruction::Push {
5599                source: Operand::Int(2),
5600            },
5601            Instruction::Push {
5602                source: Operand::Int(3),
5603            },
5604            Instruction::MakeList {
5605                length: 3,
5606                dest: Register(0),
5607            },
5608            // Match [H | T] binding H to R1, T to R2
5609            Instruction::Match {
5610                source: Register(0),
5611                pattern: Pattern::ListCons {
5612                    head: Box::new(Pattern::Variable(Register(1))),
5613                    tail: Box::new(Pattern::Variable(Register(2))),
5614                },
5615                fail_target: 7,
5616            },
5617            // Success
5618            Instruction::LoadInt {
5619                value: 1,
5620                dest: Register(3),
5621            },
5622            Instruction::End,
5623            // Fail
5624            Instruction::LoadInt {
5625                value: 0,
5626                dest: Register(3),
5627            },
5628            Instruction::End,
5629        ];
5630
5631        scheduler.spawn(program);
5632        run_to_idle(&mut scheduler);
5633
5634        let process = scheduler.processes.get(&Pid(0)).unwrap();
5635        assert_eq!(process.registers[1], Value::Int(1)); // Head
5636        assert_eq!(
5637            process.registers[2],
5638            Value::List(vec![Value::Int(2), Value::Int(3)])
5639        ); // Tail
5640        assert_eq!(process.registers[3], Value::Int(1)); // Success
5641    }
5642
5643    #[test]
5644    fn test_match_list_cons_on_empty_fails() {
5645        let mut scheduler = Scheduler::new();
5646
5647        let program = vec![
5648            // Create empty list
5649            Instruction::MakeList {
5650                length: 0,
5651                dest: Register(0),
5652            },
5653            // Try to match [H | T] - should fail
5654            Instruction::Match {
5655                source: Register(0),
5656                pattern: Pattern::ListCons {
5657                    head: Box::new(Pattern::Variable(Register(1))),
5658                    tail: Box::new(Pattern::Variable(Register(2))),
5659                },
5660                fail_target: 4,
5661            },
5662            Instruction::LoadInt {
5663                value: 1,
5664                dest: Register(3),
5665            },
5666            Instruction::End,
5667            // Fail path
5668            Instruction::LoadInt {
5669                value: 0,
5670                dest: Register(3),
5671            },
5672            Instruction::End,
5673        ];
5674
5675        scheduler.spawn(program);
5676        run_to_idle(&mut scheduler);
5677
5678        let process = scheduler.processes.get(&Pid(0)).unwrap();
5679        assert_eq!(process.registers[3], Value::Int(0)); // Fail path taken
5680    }
5681
5682    #[test]
5683    fn test_match_list_nested() {
5684        let mut scheduler = Scheduler::new();
5685
5686        // Match [1, 2 | Rest]
5687        let program = vec![
5688            // Create list [1, 2, 3, 4]
5689            Instruction::Push {
5690                source: Operand::Int(1),
5691            },
5692            Instruction::Push {
5693                source: Operand::Int(2),
5694            },
5695            Instruction::Push {
5696                source: Operand::Int(3),
5697            },
5698            Instruction::Push {
5699                source: Operand::Int(4),
5700            },
5701            Instruction::MakeList {
5702                length: 4,
5703                dest: Register(0),
5704            },
5705            // Match [1, 2 | Rest] - first must be 1, second must be 2, rest bound
5706            Instruction::Match {
5707                source: Register(0),
5708                pattern: Pattern::ListCons {
5709                    head: Box::new(Pattern::Int(1)),
5710                    tail: Box::new(Pattern::ListCons {
5711                        head: Box::new(Pattern::Int(2)),
5712                        tail: Box::new(Pattern::Variable(Register(1))),
5713                    }),
5714                },
5715                fail_target: 8,
5716            },
5717            // Success
5718            Instruction::LoadInt {
5719                value: 1,
5720                dest: Register(2),
5721            },
5722            Instruction::End,
5723            // Fail
5724            Instruction::LoadInt {
5725                value: 0,
5726                dest: Register(2),
5727            },
5728            Instruction::End,
5729        ];
5730
5731        scheduler.spawn(program);
5732        run_to_idle(&mut scheduler);
5733
5734        let process = scheduler.processes.get(&Pid(0)).unwrap();
5735        assert_eq!(
5736            process.registers[1],
5737            Value::List(vec![Value::Int(3), Value::Int(4)])
5738        ); // Rest
5739        assert_eq!(process.registers[2], Value::Int(1)); // Success
5740    }
5741
5742    // ========== Module Tests ==========
5743
5744    #[test]
5745    fn test_module_load() {
5746        use crate::Module;
5747
5748        let mut scheduler = Scheduler::new();
5749
5750        let mut math = Module::new("math".to_string());
5751        math.add_function(
5752            "identity".to_string(),
5753            1,
5754            vec![
5755                // Just return the argument (already in R0)
5756                Instruction::Return,
5757            ],
5758        );
5759        math.export("identity", 1);
5760
5761        assert!(scheduler.load_module(math).is_ok());
5762        assert!(scheduler.modules.contains_key("math"));
5763    }
5764
5765    #[test]
5766    fn test_call_mfa() {
5767        use crate::Module;
5768
5769        let mut scheduler = Scheduler::new();
5770
5771        // Create a math module with add_one function
5772        let mut math = Module::new("math".to_string());
5773        math.add_function(
5774            "add_one".to_string(),
5775            1,
5776            vec![
5777                // R0 contains the argument, add 1 and return
5778                Instruction::Add {
5779                    a: Operand::Reg(Register(0)),
5780                    b: Operand::Int(1),
5781                    dest: Register(0),
5782                },
5783                Instruction::Return,
5784            ],
5785        );
5786        math.export("add_one", 1);
5787        scheduler.load_module(math).unwrap();
5788
5789        // Spawn a process that calls math:add_one(5)
5790        let program = vec![
5791            Instruction::LoadInt {
5792                value: 5,
5793                dest: Register(0),
5794            },
5795            Instruction::CallMFA {
5796                module: "math".to_string(),
5797                function: "add_one".to_string(),
5798                arity: 1,
5799            },
5800            // R0 should now be 6
5801            Instruction::End,
5802        ];
5803
5804        scheduler.spawn(program);
5805        run_to_idle(&mut scheduler);
5806
5807        let process = scheduler.processes.get(&Pid(0)).unwrap();
5808        assert_eq!(process.registers[0], Value::Int(6));
5809    }
5810
5811    #[test]
5812    fn test_call_local() {
5813        use crate::Module;
5814
5815        let mut scheduler = Scheduler::new();
5816
5817        // Create a module with two functions
5818        let mut math = Module::new("math".to_string());
5819
5820        // double(X) -> X * 2
5821        math.add_function(
5822            "double".to_string(),
5823            1,
5824            vec![
5825                Instruction::Mul {
5826                    a: Operand::Reg(Register(0)),
5827                    b: Operand::Int(2),
5828                    dest: Register(0),
5829                },
5830                Instruction::Return,
5831            ],
5832        );
5833
5834        // quadruple(X) -> double(double(X))
5835        math.add_function(
5836            "quadruple".to_string(),
5837            1,
5838            vec![
5839                Instruction::CallLocal {
5840                    function: "double".to_string(),
5841                    arity: 1,
5842                },
5843                Instruction::CallLocal {
5844                    function: "double".to_string(),
5845                    arity: 1,
5846                },
5847                Instruction::Return,
5848            ],
5849        );
5850        math.export("quadruple", 1);
5851        scheduler.load_module(math).unwrap();
5852
5853        // Call quadruple(3) -> 12
5854        let program = vec![
5855            Instruction::LoadInt {
5856                value: 3,
5857                dest: Register(0),
5858            },
5859            Instruction::CallMFA {
5860                module: "math".to_string(),
5861                function: "quadruple".to_string(),
5862                arity: 1,
5863            },
5864            Instruction::End,
5865        ];
5866
5867        scheduler.spawn(program);
5868        run_to_idle(&mut scheduler);
5869
5870        let process = scheduler.processes.get(&Pid(0)).unwrap();
5871        assert_eq!(process.registers[0], Value::Int(12));
5872    }
5873
5874    #[test]
5875    fn test_tail_call_no_stack_growth() {
5876        use crate::Module;
5877
5878        let mut scheduler = Scheduler::new();
5879
5880        // Create a module with a tail-recursive countdown
5881        let mut counter = Module::new("counter".to_string());
5882
5883        // countdown(0) -> 0
5884        // countdown(N) -> countdown(N-1)
5885        counter.add_function(
5886            "countdown".to_string(),
5887            1,
5888            vec![
5889                // Check if N == 0
5890                Instruction::Eq {
5891                    a: Operand::Reg(Register(0)),
5892                    b: Operand::Int(0),
5893                    dest: Register(1),
5894                },
5895                Instruction::JumpIf {
5896                    cond: Operand::Reg(Register(1)),
5897                    target: 5, // Return 0
5898                },
5899                // N - 1
5900                Instruction::Sub {
5901                    a: Operand::Reg(Register(0)),
5902                    b: Operand::Int(1),
5903                    dest: Register(0),
5904                },
5905                // Tail call countdown(N-1)
5906                Instruction::TailCallLocal {
5907                    function: "countdown".to_string(),
5908                    arity: 1,
5909                },
5910                // Should never reach here
5911                Instruction::End,
5912                // Return 0
5913                Instruction::LoadInt {
5914                    value: 0,
5915                    dest: Register(0),
5916                },
5917                Instruction::Return,
5918            ],
5919        );
5920        counter.export("countdown", 1);
5921        scheduler.load_module(counter).unwrap();
5922
5923        // Call countdown(1000) - should not overflow stack
5924        let program = vec![
5925            Instruction::LoadInt {
5926                value: 1000,
5927                dest: Register(0),
5928            },
5929            Instruction::CallMFA {
5930                module: "counter".to_string(),
5931                function: "countdown".to_string(),
5932                arity: 1,
5933            },
5934            Instruction::End,
5935        ];
5936
5937        scheduler.spawn(program);
5938        run_to_idle(&mut scheduler);
5939
5940        let process = scheduler.processes.get(&Pid(0)).unwrap();
5941        assert_eq!(process.registers[0], Value::Int(0));
5942        // Call stack should be empty (all tail calls)
5943        assert!(process.call_stack.is_empty());
5944    }
5945
5946    #[test]
5947    fn test_spawn_mfa() {
5948        use crate::Module;
5949
5950        let mut scheduler = Scheduler::new();
5951
5952        // Create a module with a simple worker function
5953        let mut worker = Module::new("worker".to_string());
5954        worker.add_function(
5955            "start".to_string(),
5956            1,
5957            vec![
5958                // Just double the argument and end
5959                Instruction::Mul {
5960                    a: Operand::Reg(Register(0)),
5961                    b: Operand::Int(2),
5962                    dest: Register(0),
5963                },
5964                Instruction::End,
5965            ],
5966        );
5967        worker.export("start", 1);
5968        scheduler.load_module(worker).unwrap();
5969
5970        // Spawn a worker with argument 21
5971        let program = vec![
5972            Instruction::LoadInt {
5973                value: 21,
5974                dest: Register(0),
5975            },
5976            Instruction::SpawnMFA {
5977                module: "worker".to_string(),
5978                function: "start".to_string(),
5979                arity: 1,
5980                dest: Register(1),
5981            },
5982            Instruction::End,
5983        ];
5984
5985        scheduler.spawn(program);
5986        run_to_idle(&mut scheduler);
5987
5988        // Check the child process
5989        let child = scheduler.processes.get(&Pid(1)).unwrap();
5990        assert_eq!(child.registers[0], Value::Int(42));
5991        assert_eq!(child.status, ProcessStatus::Done);
5992    }
5993
5994    #[test]
5995    fn test_make_fun_and_apply() {
5996        use crate::Module;
5997
5998        let mut scheduler = Scheduler::new();
5999
6000        // Create a module with a double function
6001        let mut math = Module::new("math".to_string());
6002        math.add_function(
6003            "double".to_string(),
6004            1,
6005            vec![
6006                Instruction::Mul {
6007                    a: Operand::Reg(Register(0)),
6008                    b: Operand::Int(2),
6009                    dest: Register(0),
6010                },
6011                Instruction::Return,
6012            ],
6013        );
6014        math.export("double", 1);
6015        scheduler.load_module(math).unwrap();
6016
6017        // Create a function reference and apply it
6018        let program = vec![
6019            // Make a fun reference to math:double/1
6020            Instruction::MakeFun {
6021                module: "math".to_string(),
6022                function: "double".to_string(),
6023                arity: 1,
6024                dest: Register(7),
6025            },
6026            // Put argument in R0
6027            Instruction::LoadInt {
6028                value: 21,
6029                dest: Register(0),
6030            },
6031            // Apply the fun
6032            Instruction::Apply {
6033                fun: Register(7),
6034                arity: 1,
6035            },
6036            Instruction::End,
6037        ];
6038
6039        scheduler.spawn(program);
6040        run_to_idle(&mut scheduler);
6041
6042        let process = scheduler.processes.get(&Pid(0)).unwrap();
6043        assert_eq!(process.registers[0], Value::Int(42));
6044    }
6045
6046    #[test]
6047    fn test_cross_module_call() {
6048        use crate::Module;
6049
6050        let mut scheduler = Scheduler::new();
6051
6052        // Module A calls Module B
6053        let mut mod_a = Module::new("mod_a".to_string());
6054        mod_a.add_function(
6055            "call_b".to_string(),
6056            1,
6057            vec![
6058                Instruction::CallMFA {
6059                    module: "mod_b".to_string(),
6060                    function: "add_ten".to_string(),
6061                    arity: 1,
6062                },
6063                Instruction::Return,
6064            ],
6065        );
6066        mod_a.export("call_b", 1);
6067
6068        let mut mod_b = Module::new("mod_b".to_string());
6069        mod_b.add_function(
6070            "add_ten".to_string(),
6071            1,
6072            vec![
6073                Instruction::Add {
6074                    a: Operand::Reg(Register(0)),
6075                    b: Operand::Int(10),
6076                    dest: Register(0),
6077                },
6078                Instruction::Return,
6079            ],
6080        );
6081        mod_b.export("add_ten", 1);
6082
6083        scheduler.load_module(mod_a).unwrap();
6084        scheduler.load_module(mod_b).unwrap();
6085
6086        let program = vec![
6087            Instruction::LoadInt {
6088                value: 5,
6089                dest: Register(0),
6090            },
6091            Instruction::CallMFA {
6092                module: "mod_a".to_string(),
6093                function: "call_b".to_string(),
6094                arity: 1,
6095            },
6096            Instruction::End,
6097        ];
6098
6099        scheduler.spawn(program);
6100        run_to_idle(&mut scheduler);
6101
6102        let process = scheduler.processes.get(&Pid(0)).unwrap();
6103        assert_eq!(process.registers[0], Value::Int(15));
6104    }
6105
6106    #[test]
6107    fn test_make_closure_and_apply() {
6108        use crate::Module;
6109
6110        let mut scheduler = Scheduler::new();
6111
6112        // Create module with a function that multiplies explicit arg by captured value
6113        // multiply_impl/2: R0 = explicit arg, R1 = captured multiplier
6114        let mut math = Module::new("math".to_string());
6115        math.add_function(
6116            "multiply_impl".to_string(),
6117            2, // total arity = 1 explicit + 1 captured
6118            vec![
6119                Instruction::Mul {
6120                    a: Operand::Reg(Register(0)),
6121                    b: Operand::Reg(Register(1)),
6122                    dest: Register(0),
6123                },
6124                Instruction::Return,
6125            ],
6126        );
6127        math.export("multiply_impl", 2);
6128        scheduler.load_module(math).unwrap();
6129
6130        // Program: create closure capturing R1=5, apply it to R0=7
6131        let program = vec![
6132            // R1 = 5 (the multiplier to capture)
6133            Instruction::LoadInt {
6134                value: 5,
6135                dest: Register(1),
6136            },
6137            // R2 = closure capturing R1
6138            Instruction::MakeClosure {
6139                module: "math".to_string(),
6140                function: "multiply_impl".to_string(),
6141                arity: 1, // explicit arity
6142                captures: vec![Register(1)],
6143                dest: Register(2),
6144            },
6145            // R0 = 7 (the value to multiply)
6146            Instruction::LoadInt {
6147                value: 7,
6148                dest: Register(0),
6149            },
6150            // Apply closure in R2 with arity 1
6151            Instruction::Apply {
6152                fun: Register(2),
6153                arity: 1,
6154            },
6155            // R0 should now be 35
6156            Instruction::End,
6157        ];
6158
6159        scheduler.spawn(program);
6160        run_to_idle(&mut scheduler);
6161
6162        let process = scheduler.processes.get(&Pid(0)).unwrap();
6163        assert_eq!(process.registers[0], Value::Int(35));
6164    }
6165
6166    #[test]
6167    fn test_closure_with_multiple_captures() {
6168        use crate::Module;
6169
6170        let mut scheduler = Scheduler::new();
6171
6172        // add_three/3: R0 + R1 + R2
6173        let mut math = Module::new("math".to_string());
6174        math.add_function(
6175            "add_three".to_string(),
6176            3, // 1 explicit + 2 captured
6177            vec![
6178                Instruction::Add {
6179                    a: Operand::Reg(Register(0)),
6180                    b: Operand::Reg(Register(1)),
6181                    dest: Register(0),
6182                },
6183                Instruction::Add {
6184                    a: Operand::Reg(Register(0)),
6185                    b: Operand::Reg(Register(2)),
6186                    dest: Register(0),
6187                },
6188                Instruction::Return,
6189            ],
6190        );
6191        math.export("add_three", 3);
6192        scheduler.load_module(math).unwrap();
6193
6194        let program = vec![
6195            // Capture values: R3=10, R4=20
6196            Instruction::LoadInt {
6197                value: 10,
6198                dest: Register(3),
6199            },
6200            Instruction::LoadInt {
6201                value: 20,
6202                dest: Register(4),
6203            },
6204            // Create closure capturing R3 and R4
6205            Instruction::MakeClosure {
6206                module: "math".to_string(),
6207                function: "add_three".to_string(),
6208                arity: 1,
6209                captures: vec![Register(3), Register(4)],
6210                dest: Register(5),
6211            },
6212            // R0 = 5 (explicit arg)
6213            Instruction::LoadInt {
6214                value: 5,
6215                dest: Register(0),
6216            },
6217            // Apply: 5 + 10 + 20 = 35
6218            Instruction::Apply {
6219                fun: Register(5),
6220                arity: 1,
6221            },
6222            Instruction::End,
6223        ];
6224
6225        scheduler.spawn(program);
6226        run_to_idle(&mut scheduler);
6227
6228        let process = scheduler.processes.get(&Pid(0)).unwrap();
6229        assert_eq!(process.registers[0], Value::Int(35));
6230    }
6231
6232    #[test]
6233    fn test_closure_values_are_copied() {
6234        use crate::Module;
6235
6236        let mut scheduler = Scheduler::new();
6237
6238        // Simple function that returns its captured value
6239        // get_captured/1: returns R0 (which is the captured value)
6240        let mut mod_test = Module::new("test".to_string());
6241        mod_test.add_function(
6242            "get_captured".to_string(),
6243            1, // 0 explicit + 1 captured
6244            vec![Instruction::Return],
6245        );
6246        mod_test.export("get_captured", 1);
6247        scheduler.load_module(mod_test).unwrap();
6248
6249        let program = vec![
6250            // R1 = 42
6251            Instruction::LoadInt {
6252                value: 42,
6253                dest: Register(1),
6254            },
6255            // Create closure capturing R1
6256            Instruction::MakeClosure {
6257                module: "test".to_string(),
6258                function: "get_captured".to_string(),
6259                arity: 0,
6260                captures: vec![Register(1)],
6261                dest: Register(2),
6262            },
6263            // Change R1 to 100 (should not affect captured value)
6264            Instruction::LoadInt {
6265                value: 100,
6266                dest: Register(1),
6267            },
6268            // Apply closure (no explicit args)
6269            Instruction::Apply {
6270                fun: Register(2),
6271                arity: 0,
6272            },
6273            // R0 should be 42 (the captured value), not 100
6274            Instruction::End,
6275        ];
6276
6277        scheduler.spawn(program);
6278        run_to_idle(&mut scheduler);
6279
6280        let process = scheduler.processes.get(&Pid(0)).unwrap();
6281        assert_eq!(process.registers[0], Value::Int(42));
6282    }
6283
6284    #[test]
6285    fn test_closure_arity_mismatch_crashes() {
6286        use crate::Module;
6287
6288        let mut scheduler = Scheduler::new();
6289
6290        let mut math = Module::new("math".to_string());
6291        math.add_function(
6292            "add".to_string(),
6293            2,
6294            vec![
6295                Instruction::Add {
6296                    a: Operand::Reg(Register(0)),
6297                    b: Operand::Reg(Register(1)),
6298                    dest: Register(0),
6299                },
6300                Instruction::Return,
6301            ],
6302        );
6303        math.export("add", 2);
6304        scheduler.load_module(math).unwrap();
6305
6306        let program = vec![
6307            Instruction::LoadInt {
6308                value: 5,
6309                dest: Register(1),
6310            },
6311            Instruction::MakeClosure {
6312                module: "math".to_string(),
6313                function: "add".to_string(),
6314                arity: 1, // closure expects 1 explicit arg
6315                captures: vec![Register(1)],
6316                dest: Register(2),
6317            },
6318            // Apply with wrong arity (2 instead of 1)
6319            Instruction::Apply {
6320                fun: Register(2),
6321                arity: 2, // Wrong!
6322            },
6323            Instruction::End,
6324        ];
6325
6326        scheduler.spawn(program);
6327        run_to_idle(&mut scheduler);
6328
6329        let process = scheduler.processes.get(&Pid(0)).unwrap();
6330        assert_eq!(process.status, ProcessStatus::Crashed);
6331    }
6332
6333    // ========== List BIFs Tests ==========
6334
6335    #[test]
6336    fn test_list_length() {
6337        let mut scheduler = Scheduler::new();
6338
6339        let program = vec![
6340            // Create list [1, 2, 3]
6341            Instruction::Push {
6342                source: Operand::Int(1),
6343            },
6344            Instruction::Push {
6345                source: Operand::Int(2),
6346            },
6347            Instruction::Push {
6348                source: Operand::Int(3),
6349            },
6350            Instruction::MakeList {
6351                length: 3,
6352                dest: Register(0),
6353            },
6354            Instruction::ListLength {
6355                list: Register(0),
6356                dest: Register(1),
6357            },
6358            // Empty list length
6359            Instruction::MakeList {
6360                length: 0,
6361                dest: Register(2),
6362            },
6363            Instruction::ListLength {
6364                list: Register(2),
6365                dest: Register(3),
6366            },
6367            Instruction::End,
6368        ];
6369
6370        scheduler.spawn(program);
6371        run_to_idle(&mut scheduler);
6372
6373        let process = scheduler.processes.get(&Pid(0)).unwrap();
6374        assert_eq!(process.registers[1], Value::Int(3));
6375        assert_eq!(process.registers[3], Value::Int(0));
6376    }
6377
6378    #[test]
6379    fn test_list_append() {
6380        let mut scheduler = Scheduler::new();
6381
6382        let program = vec![
6383            // Create list [1, 2]
6384            Instruction::Push {
6385                source: Operand::Int(1),
6386            },
6387            Instruction::Push {
6388                source: Operand::Int(2),
6389            },
6390            Instruction::MakeList {
6391                length: 2,
6392                dest: Register(0),
6393            },
6394            // Create list [3, 4]
6395            Instruction::Push {
6396                source: Operand::Int(3),
6397            },
6398            Instruction::Push {
6399                source: Operand::Int(4),
6400            },
6401            Instruction::MakeList {
6402                length: 2,
6403                dest: Register(1),
6404            },
6405            // Append [1, 2] ++ [3, 4]
6406            Instruction::ListAppend {
6407                a: Register(0),
6408                b: Register(1),
6409                dest: Register(2),
6410            },
6411            Instruction::End,
6412        ];
6413
6414        scheduler.spawn(program);
6415        run_to_idle(&mut scheduler);
6416
6417        let process = scheduler.processes.get(&Pid(0)).unwrap();
6418        assert_eq!(
6419            process.registers[2],
6420            Value::List(vec![
6421                Value::Int(1),
6422                Value::Int(2),
6423                Value::Int(3),
6424                Value::Int(4)
6425            ])
6426        );
6427    }
6428
6429    #[test]
6430    fn test_list_reverse() {
6431        let mut scheduler = Scheduler::new();
6432
6433        let program = vec![
6434            // Create list [1, 2, 3]
6435            Instruction::Push {
6436                source: Operand::Int(1),
6437            },
6438            Instruction::Push {
6439                source: Operand::Int(2),
6440            },
6441            Instruction::Push {
6442                source: Operand::Int(3),
6443            },
6444            Instruction::MakeList {
6445                length: 3,
6446                dest: Register(0),
6447            },
6448            Instruction::ListReverse {
6449                list: Register(0),
6450                dest: Register(1),
6451            },
6452            Instruction::End,
6453        ];
6454
6455        scheduler.spawn(program);
6456        run_to_idle(&mut scheduler);
6457
6458        let process = scheduler.processes.get(&Pid(0)).unwrap();
6459        assert_eq!(
6460            process.registers[1],
6461            Value::List(vec![Value::Int(3), Value::Int(2), Value::Int(1)])
6462        );
6463    }
6464
6465    #[test]
6466    fn test_list_nth() {
6467        let mut scheduler = Scheduler::new();
6468
6469        let program = vec![
6470            // Create list [10, 20, 30]
6471            Instruction::Push {
6472                source: Operand::Int(10),
6473            },
6474            Instruction::Push {
6475                source: Operand::Int(20),
6476            },
6477            Instruction::Push {
6478                source: Operand::Int(30),
6479            },
6480            Instruction::MakeList {
6481                length: 3,
6482                dest: Register(0),
6483            },
6484            // Get element at index 0
6485            Instruction::LoadInt {
6486                value: 0,
6487                dest: Register(1),
6488            },
6489            Instruction::ListNth {
6490                list: Register(0),
6491                n: Register(1),
6492                dest: Register(2),
6493            },
6494            // Get element at index 2
6495            Instruction::LoadInt {
6496                value: 2,
6497                dest: Register(3),
6498            },
6499            Instruction::ListNth {
6500                list: Register(0),
6501                n: Register(3),
6502                dest: Register(4),
6503            },
6504            Instruction::End,
6505        ];
6506
6507        scheduler.spawn(program);
6508        run_to_idle(&mut scheduler);
6509
6510        let process = scheduler.processes.get(&Pid(0)).unwrap();
6511        assert_eq!(process.registers[2], Value::Int(10)); // index 0
6512        assert_eq!(process.registers[4], Value::Int(30)); // index 2
6513    }
6514
6515    #[test]
6516    fn test_list_nth_out_of_bounds_crashes() {
6517        let mut scheduler = Scheduler::new();
6518
6519        let program = vec![
6520            // Create list [1, 2]
6521            Instruction::Push {
6522                source: Operand::Int(1),
6523            },
6524            Instruction::Push {
6525                source: Operand::Int(2),
6526            },
6527            Instruction::MakeList {
6528                length: 2,
6529                dest: Register(0),
6530            },
6531            // Try to get element at index 5 (out of bounds)
6532            Instruction::LoadInt {
6533                value: 5,
6534                dest: Register(1),
6535            },
6536            Instruction::ListNth {
6537                list: Register(0),
6538                n: Register(1),
6539                dest: Register(2),
6540            },
6541            Instruction::End,
6542        ];
6543
6544        scheduler.spawn(program);
6545        run_to_idle(&mut scheduler);
6546
6547        let process = scheduler.processes.get(&Pid(0)).unwrap();
6548        assert_eq!(process.status, ProcessStatus::Crashed);
6549    }
6550
6551    #[test]
6552    fn test_list_member() {
6553        let mut scheduler = Scheduler::new();
6554
6555        let program = vec![
6556            // Create list [1, 2, 3]
6557            Instruction::Push {
6558                source: Operand::Int(1),
6559            },
6560            Instruction::Push {
6561                source: Operand::Int(2),
6562            },
6563            Instruction::Push {
6564                source: Operand::Int(3),
6565            },
6566            Instruction::MakeList {
6567                length: 3,
6568                dest: Register(0),
6569            },
6570            // Check if 2 is a member
6571            Instruction::LoadInt {
6572                value: 2,
6573                dest: Register(1),
6574            },
6575            Instruction::ListMember {
6576                elem: Register(1),
6577                list: Register(0),
6578                dest: Register(2),
6579            },
6580            // Check if 5 is a member
6581            Instruction::LoadInt {
6582                value: 5,
6583                dest: Register(3),
6584            },
6585            Instruction::ListMember {
6586                elem: Register(3),
6587                list: Register(0),
6588                dest: Register(4),
6589            },
6590            Instruction::End,
6591        ];
6592
6593        scheduler.spawn(program);
6594        run_to_idle(&mut scheduler);
6595
6596        let process = scheduler.processes.get(&Pid(0)).unwrap();
6597        assert_eq!(process.registers[2], Value::Int(1)); // 2 is a member
6598        assert_eq!(process.registers[4], Value::Int(0)); // 5 is not a member
6599    }
6600
6601    #[test]
6602    fn test_list_append_with_empty() {
6603        let mut scheduler = Scheduler::new();
6604
6605        let program = vec![
6606            // Create list [1, 2]
6607            Instruction::Push {
6608                source: Operand::Int(1),
6609            },
6610            Instruction::Push {
6611                source: Operand::Int(2),
6612            },
6613            Instruction::MakeList {
6614                length: 2,
6615                dest: Register(0),
6616            },
6617            // Create empty list
6618            Instruction::MakeList {
6619                length: 0,
6620                dest: Register(1),
6621            },
6622            // Append [1, 2] ++ []
6623            Instruction::ListAppend {
6624                a: Register(0),
6625                b: Register(1),
6626                dest: Register(2),
6627            },
6628            // Append [] ++ [1, 2]
6629            Instruction::ListAppend {
6630                a: Register(1),
6631                b: Register(0),
6632                dest: Register(3),
6633            },
6634            Instruction::End,
6635        ];
6636
6637        scheduler.spawn(program);
6638        run_to_idle(&mut scheduler);
6639
6640        let process = scheduler.processes.get(&Pid(0)).unwrap();
6641        assert_eq!(
6642            process.registers[2],
6643            Value::List(vec![Value::Int(1), Value::Int(2)])
6644        );
6645        assert_eq!(
6646            process.registers[3],
6647            Value::List(vec![Value::Int(1), Value::Int(2)])
6648        );
6649    }
6650
6651    // ========== Type Checking Tests ==========
6652
6653    #[test]
6654    fn test_is_integer() {
6655        let mut scheduler = Scheduler::new();
6656
6657        let program = vec![
6658            // R0 = 42 (integer)
6659            Instruction::LoadInt {
6660                value: 42,
6661                dest: Register(0),
6662            },
6663            Instruction::IsInteger {
6664                source: Register(0),
6665                dest: Register(1),
6666            },
6667            // R2 = :atom (not an integer)
6668            Instruction::LoadAtom {
6669                name: "test".to_string(),
6670                dest: Register(2),
6671            },
6672            Instruction::IsInteger {
6673                source: Register(2),
6674                dest: Register(3),
6675            },
6676            Instruction::End,
6677        ];
6678
6679        scheduler.spawn(program);
6680        run_to_idle(&mut scheduler);
6681
6682        let process = scheduler.processes.get(&Pid(0)).unwrap();
6683        assert_eq!(process.registers[1], Value::Int(1)); // 42 is an integer
6684        assert_eq!(process.registers[3], Value::Int(0)); // :test is not an integer
6685    }
6686
6687    #[test]
6688    fn test_is_atom() {
6689        let mut scheduler = Scheduler::new();
6690
6691        let program = vec![
6692            // R0 = :ok (atom)
6693            Instruction::LoadAtom {
6694                name: "ok".to_string(),
6695                dest: Register(0),
6696            },
6697            Instruction::IsAtom {
6698                source: Register(0),
6699                dest: Register(1),
6700            },
6701            // R2 = 42 (not an atom)
6702            Instruction::LoadInt {
6703                value: 42,
6704                dest: Register(2),
6705            },
6706            Instruction::IsAtom {
6707                source: Register(2),
6708                dest: Register(3),
6709            },
6710            Instruction::End,
6711        ];
6712
6713        scheduler.spawn(program);
6714        run_to_idle(&mut scheduler);
6715
6716        let process = scheduler.processes.get(&Pid(0)).unwrap();
6717        assert_eq!(process.registers[1], Value::Int(1)); // :ok is an atom
6718        assert_eq!(process.registers[3], Value::Int(0)); // 42 is not an atom
6719    }
6720
6721    #[test]
6722    fn test_is_tuple() {
6723        let mut scheduler = Scheduler::new();
6724
6725        let program = vec![
6726            // Create tuple {1, 2}
6727            Instruction::Push {
6728                source: Operand::Int(1),
6729            },
6730            Instruction::Push {
6731                source: Operand::Int(2),
6732            },
6733            Instruction::MakeTuple {
6734                arity: 2,
6735                dest: Register(0),
6736            },
6737            Instruction::IsTuple {
6738                source: Register(0),
6739                dest: Register(1),
6740            },
6741            // R2 = 42 (not a tuple)
6742            Instruction::LoadInt {
6743                value: 42,
6744                dest: Register(2),
6745            },
6746            Instruction::IsTuple {
6747                source: Register(2),
6748                dest: Register(3),
6749            },
6750            Instruction::End,
6751        ];
6752
6753        scheduler.spawn(program);
6754        run_to_idle(&mut scheduler);
6755
6756        let process = scheduler.processes.get(&Pid(0)).unwrap();
6757        assert_eq!(process.registers[1], Value::Int(1)); // {1, 2} is a tuple
6758        assert_eq!(process.registers[3], Value::Int(0)); // 42 is not a tuple
6759    }
6760
6761    #[test]
6762    fn test_is_list() {
6763        let mut scheduler = Scheduler::new();
6764
6765        let program = vec![
6766            // Create list [1, 2]
6767            Instruction::Push {
6768                source: Operand::Int(1),
6769            },
6770            Instruction::Push {
6771                source: Operand::Int(2),
6772            },
6773            Instruction::MakeList {
6774                length: 2,
6775                dest: Register(0),
6776            },
6777            Instruction::IsList {
6778                source: Register(0),
6779                dest: Register(1),
6780            },
6781            // Empty list is also a list
6782            Instruction::MakeList {
6783                length: 0,
6784                dest: Register(2),
6785            },
6786            Instruction::IsList {
6787                source: Register(2),
6788                dest: Register(3),
6789            },
6790            // R4 = 42 (not a list)
6791            Instruction::LoadInt {
6792                value: 42,
6793                dest: Register(4),
6794            },
6795            Instruction::IsList {
6796                source: Register(4),
6797                dest: Register(5),
6798            },
6799            Instruction::End,
6800        ];
6801
6802        scheduler.spawn(program);
6803        run_to_idle(&mut scheduler);
6804
6805        let process = scheduler.processes.get(&Pid(0)).unwrap();
6806        assert_eq!(process.registers[1], Value::Int(1)); // [1, 2] is a list
6807        assert_eq!(process.registers[3], Value::Int(1)); // [] is a list
6808        assert_eq!(process.registers[5], Value::Int(0)); // 42 is not a list
6809    }
6810
6811    #[test]
6812    fn test_is_pid() {
6813        let mut scheduler = Scheduler::new();
6814
6815        let program = vec![
6816            // Spawn a process to get a PID
6817            Instruction::Spawn {
6818                code: vec![Instruction::End],
6819                dest: Register(0),
6820            },
6821            Instruction::IsPid {
6822                source: Register(0),
6823                dest: Register(1),
6824            },
6825            // R2 = 42 (not a PID)
6826            Instruction::LoadInt {
6827                value: 42,
6828                dest: Register(2),
6829            },
6830            Instruction::IsPid {
6831                source: Register(2),
6832                dest: Register(3),
6833            },
6834            Instruction::End,
6835        ];
6836
6837        scheduler.spawn(program);
6838        run_to_idle(&mut scheduler);
6839
6840        let process = scheduler.processes.get(&Pid(0)).unwrap();
6841        assert_eq!(process.registers[1], Value::Int(1)); // spawned PID is a PID
6842        assert_eq!(process.registers[3], Value::Int(0)); // 42 is not a PID
6843    }
6844
6845    #[test]
6846    fn test_is_function() {
6847        use crate::Module;
6848
6849        let mut scheduler = Scheduler::new();
6850
6851        // Create a module with a function
6852        let mut math = Module::new("math".to_string());
6853        math.add_function("id".to_string(), 1, vec![Instruction::Return]);
6854        math.export("id", 1);
6855        scheduler.load_module(math).unwrap();
6856
6857        let program = vec![
6858            // Create a function reference
6859            Instruction::MakeFun {
6860                module: "math".to_string(),
6861                function: "id".to_string(),
6862                arity: 1,
6863                dest: Register(0),
6864            },
6865            Instruction::IsFunction {
6866                source: Register(0),
6867                dest: Register(1),
6868            },
6869            // R2 = 42 (not a function)
6870            Instruction::LoadInt {
6871                value: 42,
6872                dest: Register(2),
6873            },
6874            Instruction::IsFunction {
6875                source: Register(2),
6876                dest: Register(3),
6877            },
6878            Instruction::End,
6879        ];
6880
6881        scheduler.spawn(program);
6882        run_to_idle(&mut scheduler);
6883
6884        let process = scheduler.processes.get(&Pid(0)).unwrap();
6885        assert_eq!(process.registers[1], Value::Int(1)); // fun is a function
6886        assert_eq!(process.registers[3], Value::Int(0)); // 42 is not a function
6887    }
6888
6889    #[test]
6890    fn test_is_function_closure() {
6891        use crate::Module;
6892
6893        let mut scheduler = Scheduler::new();
6894
6895        // Create a module with a function
6896        let mut math = Module::new("math".to_string());
6897        math.add_function(
6898            "add".to_string(),
6899            2,
6900            vec![
6901                Instruction::Add {
6902                    a: Operand::Reg(Register(0)),
6903                    b: Operand::Reg(Register(1)),
6904                    dest: Register(0),
6905                },
6906                Instruction::Return,
6907            ],
6908        );
6909        math.export("add", 2);
6910        scheduler.load_module(math).unwrap();
6911
6912        let program = vec![
6913            // R1 = 5 (value to capture)
6914            Instruction::LoadInt {
6915                value: 5,
6916                dest: Register(1),
6917            },
6918            // Create a closure
6919            Instruction::MakeClosure {
6920                module: "math".to_string(),
6921                function: "add".to_string(),
6922                arity: 1,
6923                captures: vec![Register(1)],
6924                dest: Register(0),
6925            },
6926            Instruction::IsFunction {
6927                source: Register(0),
6928                dest: Register(2),
6929            },
6930            Instruction::End,
6931        ];
6932
6933        scheduler.spawn(program);
6934        run_to_idle(&mut scheduler);
6935
6936        let process = scheduler.processes.get(&Pid(0)).unwrap();
6937        assert_eq!(process.registers[2], Value::Int(1)); // closure is a function
6938    }
6939
6940    #[test]
6941    fn test_type_checks_comprehensive() {
6942        let mut scheduler = Scheduler::new();
6943
6944        // Test that each type only matches its own check
6945        let program = vec![
6946            // R0 = 42 (integer)
6947            Instruction::LoadInt {
6948                value: 42,
6949                dest: Register(0),
6950            },
6951            // Create list [1]
6952            Instruction::Push {
6953                source: Operand::Int(1),
6954            },
6955            Instruction::MakeList {
6956                length: 1,
6957                dest: Register(1),
6958            },
6959            // Check integer against all types
6960            Instruction::IsInteger {
6961                source: Register(0),
6962                dest: Register(2),
6963            },
6964            Instruction::IsAtom {
6965                source: Register(0),
6966                dest: Register(3),
6967            },
6968            Instruction::IsTuple {
6969                source: Register(0),
6970                dest: Register(4),
6971            },
6972            Instruction::IsList {
6973                source: Register(0),
6974                dest: Register(5),
6975            },
6976            Instruction::IsPid {
6977                source: Register(0),
6978                dest: Register(6),
6979            },
6980            Instruction::End,
6981        ];
6982
6983        scheduler.spawn(program);
6984        run_to_idle(&mut scheduler);
6985
6986        let process = scheduler.processes.get(&Pid(0)).unwrap();
6987        assert_eq!(process.registers[2], Value::Int(1)); // is_integer(42) = true
6988        assert_eq!(process.registers[3], Value::Int(0)); // is_atom(42) = false
6989        assert_eq!(process.registers[4], Value::Int(0)); // is_tuple(42) = false
6990        assert_eq!(process.registers[5], Value::Int(0)); // is_list(42) = false
6991        assert_eq!(process.registers[6], Value::Int(0)); // is_pid(42) = false
6992    }
6993
6994    #[test]
6995    fn test_process_dictionary_put_get() {
6996        let mut scheduler = Scheduler::new();
6997
6998        let program = vec![
6999            // Put key=:foo, value=42 into dictionary
7000            Instruction::LoadAtom {
7001                name: "foo".into(),
7002                dest: Register(0),
7003            },
7004            Instruction::LoadInt {
7005                value: 42,
7006                dest: Register(1),
7007            },
7008            Instruction::PutDict {
7009                key: Register(0),
7010                value: Register(1),
7011                dest: Register(2), // old value (should be None)
7012            },
7013            // Get the value back
7014            Instruction::GetDict {
7015                key: Register(0),
7016                dest: Register(3),
7017            },
7018            Instruction::End,
7019        ];
7020
7021        scheduler.spawn(program);
7022        run_to_idle(&mut scheduler);
7023
7024        let process = scheduler.processes.get(&Pid(0)).unwrap();
7025        assert_eq!(process.registers[2], Value::None); // no old value
7026        assert_eq!(process.registers[3], Value::Int(42)); // retrieved value
7027    }
7028
7029    #[test]
7030    fn test_process_dictionary_overwrite() {
7031        let mut scheduler = Scheduler::new();
7032
7033        let program = vec![
7034            // Put key=:bar, value=100
7035            Instruction::LoadAtom {
7036                name: "bar".into(),
7037                dest: Register(0),
7038            },
7039            Instruction::LoadInt {
7040                value: 100,
7041                dest: Register(1),
7042            },
7043            Instruction::PutDict {
7044                key: Register(0),
7045                value: Register(1),
7046                dest: Register(2),
7047            },
7048            // Overwrite with value=200
7049            Instruction::LoadInt {
7050                value: 200,
7051                dest: Register(1),
7052            },
7053            Instruction::PutDict {
7054                key: Register(0),
7055                value: Register(1),
7056                dest: Register(3), // should get old value 100
7057            },
7058            // Get final value
7059            Instruction::GetDict {
7060                key: Register(0),
7061                dest: Register(4),
7062            },
7063            Instruction::End,
7064        ];
7065
7066        scheduler.spawn(program);
7067        run_to_idle(&mut scheduler);
7068
7069        let process = scheduler.processes.get(&Pid(0)).unwrap();
7070        assert_eq!(process.registers[2], Value::None); // first put: no old value
7071        assert_eq!(process.registers[3], Value::Int(100)); // second put: old value was 100
7072        assert_eq!(process.registers[4], Value::Int(200)); // current value is 200
7073    }
7074
7075    #[test]
7076    fn test_process_dictionary_erase() {
7077        let mut scheduler = Scheduler::new();
7078
7079        let program = vec![
7080            // Put key=:temp, value=999
7081            Instruction::LoadAtom {
7082                name: "temp".into(),
7083                dest: Register(0),
7084            },
7085            Instruction::LoadInt {
7086                value: 999,
7087                dest: Register(1),
7088            },
7089            Instruction::PutDict {
7090                key: Register(0),
7091                value: Register(1),
7092                dest: Register(2),
7093            },
7094            // Erase the key
7095            Instruction::EraseDict {
7096                key: Register(0),
7097                dest: Register(3), // should get 999
7098            },
7099            // Try to get erased key
7100            Instruction::GetDict {
7101                key: Register(0),
7102                dest: Register(4), // should be None
7103            },
7104            Instruction::End,
7105        ];
7106
7107        scheduler.spawn(program);
7108        run_to_idle(&mut scheduler);
7109
7110        let process = scheduler.processes.get(&Pid(0)).unwrap();
7111        assert_eq!(process.registers[3], Value::Int(999)); // erased value
7112        assert_eq!(process.registers[4], Value::None); // key no longer exists
7113    }
7114
7115    #[test]
7116    fn test_process_dictionary_get_keys() {
7117        let mut scheduler = Scheduler::new();
7118
7119        let program = vec![
7120            // Put two keys
7121            Instruction::LoadAtom {
7122                name: "key1".into(),
7123                dest: Register(0),
7124            },
7125            Instruction::LoadInt {
7126                value: 1,
7127                dest: Register(1),
7128            },
7129            Instruction::PutDict {
7130                key: Register(0),
7131                value: Register(1),
7132                dest: Register(7),
7133            },
7134            Instruction::LoadAtom {
7135                name: "key2".into(),
7136                dest: Register(2),
7137            },
7138            Instruction::LoadInt {
7139                value: 2,
7140                dest: Register(3),
7141            },
7142            Instruction::PutDict {
7143                key: Register(2),
7144                value: Register(3),
7145                dest: Register(7),
7146            },
7147            // Get all keys
7148            Instruction::GetDictKeys { dest: Register(4) },
7149            Instruction::End,
7150        ];
7151
7152        scheduler.spawn(program);
7153        run_to_idle(&mut scheduler);
7154
7155        let process = scheduler.processes.get(&Pid(0)).unwrap();
7156        match &process.registers[4] {
7157            Value::List(keys) => {
7158                assert_eq!(keys.len(), 2);
7159                // Keys should contain :key1 and :key2 (order not guaranteed)
7160                assert!(
7161                    keys.contains(&Value::Atom("key1".into()))
7162                        && keys.contains(&Value::Atom("key2".into()))
7163                );
7164            }
7165            other => panic!("Expected List, got {:?}", other),
7166        }
7167    }
7168
7169    #[test]
7170    fn test_process_dictionary_get_missing() {
7171        let mut scheduler = Scheduler::new();
7172
7173        let program = vec![
7174            // Try to get a key that doesn't exist
7175            Instruction::LoadAtom {
7176                name: "nonexistent".into(),
7177                dest: Register(0),
7178            },
7179            Instruction::GetDict {
7180                key: Register(0),
7181                dest: Register(1),
7182            },
7183            Instruction::End,
7184        ];
7185
7186        scheduler.spawn(program);
7187        run_to_idle(&mut scheduler);
7188
7189        let process = scheduler.processes.get(&Pid(0)).unwrap();
7190        assert_eq!(process.registers[1], Value::None);
7191    }
7192
7193    // ========== Map Tests ==========
7194
7195    #[test]
7196    fn test_make_map() {
7197        let mut scheduler = Scheduler::new();
7198
7199        let program = vec![
7200            // Push key1, value1, key2, value2 onto stack
7201            Instruction::LoadAtom {
7202                name: "a".into(),
7203                dest: Register(0),
7204            },
7205            Instruction::Push {
7206                source: Operand::Reg(Register(0)),
7207            },
7208            Instruction::Push {
7209                source: Operand::Int(1),
7210            },
7211            Instruction::LoadAtom {
7212                name: "b".into(),
7213                dest: Register(0),
7214            },
7215            Instruction::Push {
7216                source: Operand::Reg(Register(0)),
7217            },
7218            Instruction::Push {
7219                source: Operand::Int(2),
7220            },
7221            // Make map with 2 pairs
7222            Instruction::MakeMap {
7223                count: 2,
7224                dest: Register(1),
7225            },
7226            Instruction::End,
7227        ];
7228
7229        scheduler.spawn(program);
7230        run_to_idle(&mut scheduler);
7231
7232        let process = scheduler.processes.get(&Pid(0)).unwrap();
7233        match &process.registers[1] {
7234            Value::Map(m) => {
7235                assert_eq!(m.len(), 2);
7236                assert_eq!(m.get(&Value::Atom("a".into())), Some(&Value::Int(1)));
7237                assert_eq!(m.get(&Value::Atom("b".into())), Some(&Value::Int(2)));
7238            }
7239            other => panic!("Expected Map, got {:?}", other),
7240        }
7241    }
7242
7243    #[test]
7244    fn test_map_get() {
7245        let mut scheduler = Scheduler::new();
7246
7247        let program = vec![
7248            // Create a map %{:x => 42}
7249            Instruction::LoadAtom {
7250                name: "x".into(),
7251                dest: Register(0),
7252            },
7253            Instruction::Push {
7254                source: Operand::Reg(Register(0)),
7255            },
7256            Instruction::Push {
7257                source: Operand::Int(42),
7258            },
7259            Instruction::MakeMap {
7260                count: 1,
7261                dest: Register(1),
7262            },
7263            // Get value for key :x
7264            Instruction::MapGet {
7265                map: Register(1),
7266                key: Register(0),
7267                dest: Register(2),
7268            },
7269            Instruction::End,
7270        ];
7271
7272        scheduler.spawn(program);
7273        run_to_idle(&mut scheduler);
7274
7275        let process = scheduler.processes.get(&Pid(0)).unwrap();
7276        assert_eq!(process.registers[2], Value::Int(42));
7277    }
7278
7279    #[test]
7280    fn test_map_get_default() {
7281        let mut scheduler = Scheduler::new();
7282
7283        let program = vec![
7284            // Create empty map
7285            Instruction::MakeMap {
7286                count: 0,
7287                dest: Register(0),
7288            },
7289            // Try to get missing key with default
7290            Instruction::LoadAtom {
7291                name: "missing".into(),
7292                dest: Register(1),
7293            },
7294            Instruction::LoadInt {
7295                value: -1,
7296                dest: Register(2),
7297            },
7298            Instruction::MapGetDefault {
7299                map: Register(0),
7300                key: Register(1),
7301                default: Register(2),
7302                dest: Register(3),
7303            },
7304            Instruction::End,
7305        ];
7306
7307        scheduler.spawn(program);
7308        run_to_idle(&mut scheduler);
7309
7310        let process = scheduler.processes.get(&Pid(0)).unwrap();
7311        assert_eq!(process.registers[3], Value::Int(-1));
7312    }
7313
7314    #[test]
7315    fn test_map_put() {
7316        let mut scheduler = Scheduler::new();
7317
7318        let program = vec![
7319            // Create empty map
7320            Instruction::MakeMap {
7321                count: 0,
7322                dest: Register(0),
7323            },
7324            // Put :key => 100
7325            Instruction::LoadAtom {
7326                name: "key".into(),
7327                dest: Register(1),
7328            },
7329            Instruction::LoadInt {
7330                value: 100,
7331                dest: Register(2),
7332            },
7333            Instruction::MapPut {
7334                map: Register(0),
7335                key: Register(1),
7336                value: Register(2),
7337                dest: Register(3),
7338            },
7339            // Get value back
7340            Instruction::MapGet {
7341                map: Register(3),
7342                key: Register(1),
7343                dest: Register(4),
7344            },
7345            // Original map should be unchanged (size 0)
7346            Instruction::MapSize {
7347                map: Register(0),
7348                dest: Register(5),
7349            },
7350            // New map should have size 1
7351            Instruction::MapSize {
7352                map: Register(3),
7353                dest: Register(6),
7354            },
7355            Instruction::End,
7356        ];
7357
7358        scheduler.spawn(program);
7359        run_to_idle(&mut scheduler);
7360
7361        let process = scheduler.processes.get(&Pid(0)).unwrap();
7362        assert_eq!(process.registers[4], Value::Int(100));
7363        assert_eq!(process.registers[5], Value::Int(0)); // Original unchanged
7364        assert_eq!(process.registers[6], Value::Int(1)); // New has 1 entry
7365    }
7366
7367    #[test]
7368    fn test_map_remove() {
7369        let mut scheduler = Scheduler::new();
7370
7371        let program = vec![
7372            // Create map with one entry
7373            Instruction::LoadAtom {
7374                name: "rem".into(),
7375                dest: Register(0),
7376            },
7377            Instruction::Push {
7378                source: Operand::Reg(Register(0)),
7379            },
7380            Instruction::Push {
7381                source: Operand::Int(999),
7382            },
7383            Instruction::MakeMap {
7384                count: 1,
7385                dest: Register(1),
7386            },
7387            // Remove the key
7388            Instruction::MapRemove {
7389                map: Register(1),
7390                key: Register(0),
7391                dest: Register(2),
7392            },
7393            // Check sizes
7394            Instruction::MapSize {
7395                map: Register(1),
7396                dest: Register(3),
7397            },
7398            Instruction::MapSize {
7399                map: Register(2),
7400                dest: Register(4),
7401            },
7402            Instruction::End,
7403        ];
7404
7405        scheduler.spawn(program);
7406        run_to_idle(&mut scheduler);
7407
7408        let process = scheduler.processes.get(&Pid(0)).unwrap();
7409        assert_eq!(process.registers[3], Value::Int(1)); // Original has 1
7410        assert_eq!(process.registers[4], Value::Int(0)); // After remove has 0
7411    }
7412
7413    #[test]
7414    fn test_map_has() {
7415        let mut scheduler = Scheduler::new();
7416
7417        let program = vec![
7418            // Create map %{:exists => 1}
7419            Instruction::LoadAtom {
7420                name: "exists".into(),
7421                dest: Register(0),
7422            },
7423            Instruction::Push {
7424                source: Operand::Reg(Register(0)),
7425            },
7426            Instruction::Push {
7427                source: Operand::Int(1),
7428            },
7429            Instruction::MakeMap {
7430                count: 1,
7431                dest: Register(1),
7432            },
7433            // Check if :exists is in map
7434            Instruction::MapHas {
7435                map: Register(1),
7436                key: Register(0),
7437                dest: Register(2),
7438            },
7439            // Check if :missing is in map
7440            Instruction::LoadAtom {
7441                name: "missing".into(),
7442                dest: Register(3),
7443            },
7444            Instruction::MapHas {
7445                map: Register(1),
7446                key: Register(3),
7447                dest: Register(4),
7448            },
7449            Instruction::End,
7450        ];
7451
7452        scheduler.spawn(program);
7453        run_to_idle(&mut scheduler);
7454
7455        let process = scheduler.processes.get(&Pid(0)).unwrap();
7456        assert_eq!(process.registers[2], Value::Int(1)); // :exists is present
7457        assert_eq!(process.registers[4], Value::Int(0)); // :missing is not
7458    }
7459
7460    #[test]
7461    fn test_map_keys_values() {
7462        let mut scheduler = Scheduler::new();
7463
7464        let program = vec![
7465            // Create map %{:a => 1, :b => 2}
7466            Instruction::LoadAtom {
7467                name: "a".into(),
7468                dest: Register(0),
7469            },
7470            Instruction::Push {
7471                source: Operand::Reg(Register(0)),
7472            },
7473            Instruction::Push {
7474                source: Operand::Int(1),
7475            },
7476            Instruction::LoadAtom {
7477                name: "b".into(),
7478                dest: Register(0),
7479            },
7480            Instruction::Push {
7481                source: Operand::Reg(Register(0)),
7482            },
7483            Instruction::Push {
7484                source: Operand::Int(2),
7485            },
7486            Instruction::MakeMap {
7487                count: 2,
7488                dest: Register(1),
7489            },
7490            // Get keys and values
7491            Instruction::MapKeys {
7492                map: Register(1),
7493                dest: Register(2),
7494            },
7495            Instruction::MapValues {
7496                map: Register(1),
7497                dest: Register(3),
7498            },
7499            Instruction::End,
7500        ];
7501
7502        scheduler.spawn(program);
7503        run_to_idle(&mut scheduler);
7504
7505        let process = scheduler.processes.get(&Pid(0)).unwrap();
7506        match &process.registers[2] {
7507            Value::List(keys) => {
7508                assert_eq!(keys.len(), 2);
7509                assert!(keys.contains(&Value::Atom("a".into())));
7510                assert!(keys.contains(&Value::Atom("b".into())));
7511            }
7512            other => panic!("Expected List, got {:?}", other),
7513        }
7514        match &process.registers[3] {
7515            Value::List(vals) => {
7516                assert_eq!(vals.len(), 2);
7517                assert!(vals.contains(&Value::Int(1)));
7518                assert!(vals.contains(&Value::Int(2)));
7519            }
7520            other => panic!("Expected List, got {:?}", other),
7521        }
7522    }
7523
7524    #[test]
7525    fn test_is_map() {
7526        let mut scheduler = Scheduler::new();
7527
7528        let program = vec![
7529            // Create a map
7530            Instruction::MakeMap {
7531                count: 0,
7532                dest: Register(0),
7533            },
7534            // Check is_map on map
7535            Instruction::IsMap {
7536                source: Register(0),
7537                dest: Register(1),
7538            },
7539            // Check is_map on non-map
7540            Instruction::LoadInt {
7541                value: 42,
7542                dest: Register(2),
7543            },
7544            Instruction::IsMap {
7545                source: Register(2),
7546                dest: Register(3),
7547            },
7548            Instruction::End,
7549        ];
7550
7551        scheduler.spawn(program);
7552        run_to_idle(&mut scheduler);
7553
7554        let process = scheduler.processes.get(&Pid(0)).unwrap();
7555        assert_eq!(process.registers[1], Value::Int(1)); // map is a map
7556        assert_eq!(process.registers[3], Value::Int(0)); // int is not a map
7557    }
7558
7559    #[test]
7560    fn test_map_pattern_matching() {
7561        let mut scheduler = Scheduler::new();
7562
7563        // Create a map and match it with a pattern
7564        let program = vec![
7565            // Create map %{:name => "Alice", :age => 30}
7566            Instruction::LoadAtom {
7567                name: "name".into(),
7568                dest: Register(0),
7569            },
7570            Instruction::Push {
7571                source: Operand::Reg(Register(0)),
7572            },
7573            Instruction::LoadAtom {
7574                name: "Alice".into(),
7575                dest: Register(0),
7576            },
7577            Instruction::Push {
7578                source: Operand::Reg(Register(0)),
7579            },
7580            Instruction::LoadAtom {
7581                name: "age".into(),
7582                dest: Register(0),
7583            },
7584            Instruction::Push {
7585                source: Operand::Reg(Register(0)),
7586            },
7587            Instruction::Push {
7588                source: Operand::Int(30),
7589            },
7590            Instruction::MakeMap {
7591                count: 2,
7592                dest: Register(1),
7593            },
7594            // Match against pattern %{:age => age_val}
7595            Instruction::Match {
7596                source: Register(1),
7597                pattern: Pattern::Map(vec![(
7598                    Pattern::Atom("age".into()),
7599                    Pattern::Variable(Register(2)),
7600                )]),
7601                fail_target: 100, // Should not fail
7602            },
7603            Instruction::End,
7604        ];
7605
7606        scheduler.spawn(program);
7607        run_to_idle(&mut scheduler);
7608
7609        let process = scheduler.processes.get(&Pid(0)).unwrap();
7610        assert_eq!(process.registers[2], Value::Int(30));
7611    }
7612
7613    // ========== Reference Tests ==========
7614
7615    #[test]
7616    fn test_make_ref() {
7617        let mut scheduler = Scheduler::new();
7618
7619        let program = vec![
7620            Instruction::MakeRef { dest: Register(0) },
7621            Instruction::MakeRef { dest: Register(1) },
7622            Instruction::MakeRef { dest: Register(2) },
7623            Instruction::End,
7624        ];
7625
7626        scheduler.spawn(program);
7627        run_to_idle(&mut scheduler);
7628
7629        let process = scheduler.processes.get(&Pid(0)).unwrap();
7630
7631        // Each ref should be unique
7632        match (
7633            &process.registers[0],
7634            &process.registers[1],
7635            &process.registers[2],
7636        ) {
7637            (Value::Ref(r0), Value::Ref(r1), Value::Ref(r2)) => {
7638                assert_ne!(r0, r1);
7639                assert_ne!(r1, r2);
7640                assert_ne!(r0, r2);
7641            }
7642            _ => panic!("Expected Ref values"),
7643        }
7644    }
7645
7646    #[test]
7647    fn test_refs_unique_across_processes() {
7648        let mut scheduler = Scheduler::new();
7649
7650        // First process creates a ref
7651        let program1 = vec![Instruction::MakeRef { dest: Register(0) }, Instruction::End];
7652
7653        // Second process creates a ref
7654        let program2 = vec![Instruction::MakeRef { dest: Register(0) }, Instruction::End];
7655
7656        scheduler.spawn(program1);
7657        scheduler.spawn(program2);
7658        run_to_idle(&mut scheduler);
7659
7660        let p0 = scheduler.processes.get(&Pid(0)).unwrap();
7661        let p1 = scheduler.processes.get(&Pid(1)).unwrap();
7662
7663        // Refs from different processes should be unique
7664        match (&p0.registers[0], &p1.registers[0]) {
7665            (Value::Ref(r0), Value::Ref(r1)) => {
7666                assert_ne!(r0, r1);
7667            }
7668            _ => panic!("Expected Ref values"),
7669        }
7670    }
7671
7672    #[test]
7673    fn test_is_ref() {
7674        let mut scheduler = Scheduler::new();
7675
7676        let program = vec![
7677            // Create a ref
7678            Instruction::MakeRef { dest: Register(0) },
7679            // Check is_ref on ref
7680            Instruction::IsRef {
7681                source: Register(0),
7682                dest: Register(1),
7683            },
7684            // Check is_ref on non-ref
7685            Instruction::LoadInt {
7686                value: 42,
7687                dest: Register(2),
7688            },
7689            Instruction::IsRef {
7690                source: Register(2),
7691                dest: Register(3),
7692            },
7693            Instruction::End,
7694        ];
7695
7696        scheduler.spawn(program);
7697        run_to_idle(&mut scheduler);
7698
7699        let process = scheduler.processes.get(&Pid(0)).unwrap();
7700        assert_eq!(process.registers[1], Value::Int(1)); // ref is a ref
7701        assert_eq!(process.registers[3], Value::Int(0)); // int is not a ref
7702    }
7703
7704    #[test]
7705    fn test_ref_equality_via_copy() {
7706        let mut scheduler = Scheduler::new();
7707
7708        let program = vec![
7709            // Create a ref
7710            Instruction::MakeRef { dest: Register(0) },
7711            // Copy it
7712            Instruction::Move {
7713                source: Register(0),
7714                dest: Register(1),
7715            },
7716            Instruction::End,
7717        ];
7718
7719        scheduler.spawn(program);
7720        run_to_idle(&mut scheduler);
7721
7722        let process = scheduler.processes.get(&Pid(0)).unwrap();
7723        // Copied ref should be equal to original (same underlying value)
7724        assert_eq!(process.registers[0], process.registers[1]);
7725    }
7726
7727    #[test]
7728    fn test_ref_as_map_key() {
7729        let mut scheduler = Scheduler::new();
7730
7731        let program = vec![
7732            // Create a ref to use as key
7733            Instruction::MakeRef { dest: Register(0) },
7734            // Push ref and value onto stack
7735            Instruction::Push {
7736                source: Operand::Reg(Register(0)),
7737            },
7738            Instruction::Push {
7739                source: Operand::Int(42),
7740            },
7741            // Create map with ref as key
7742            Instruction::MakeMap {
7743                count: 1,
7744                dest: Register(1),
7745            },
7746            // Look up using the same ref
7747            Instruction::MapGet {
7748                map: Register(1),
7749                key: Register(0),
7750                dest: Register(2),
7751            },
7752            Instruction::End,
7753        ];
7754
7755        scheduler.spawn(program);
7756        run_to_idle(&mut scheduler);
7757
7758        let process = scheduler.processes.get(&Pid(0)).unwrap();
7759        // Should retrieve the value using the ref key
7760        assert_eq!(process.registers[2], Value::Int(42));
7761    }
7762
7763    // ========== Float Tests ==========
7764
7765    #[test]
7766    fn test_load_float() {
7767        let mut scheduler = Scheduler::new();
7768
7769        let program = vec![
7770            Instruction::LoadFloat {
7771                value: 3.14,
7772                dest: Register(0),
7773            },
7774            Instruction::LoadFloat {
7775                value: -2.5,
7776                dest: Register(1),
7777            },
7778            Instruction::End,
7779        ];
7780
7781        scheduler.spawn(program);
7782        run_to_idle(&mut scheduler);
7783
7784        let process = scheduler.processes.get(&Pid(0)).unwrap();
7785        assert_eq!(process.registers[0], Value::Float(3.14));
7786        assert_eq!(process.registers[1], Value::Float(-2.5));
7787    }
7788
7789    #[test]
7790    fn test_is_float() {
7791        let mut scheduler = Scheduler::new();
7792
7793        let program = vec![
7794            Instruction::LoadFloat {
7795                value: 1.5,
7796                dest: Register(0),
7797            },
7798            Instruction::IsFloat {
7799                source: Register(0),
7800                dest: Register(1),
7801            },
7802            Instruction::LoadInt {
7803                value: 42,
7804                dest: Register(2),
7805            },
7806            Instruction::IsFloat {
7807                source: Register(2),
7808                dest: Register(3),
7809            },
7810            Instruction::End,
7811        ];
7812
7813        scheduler.spawn(program);
7814        run_to_idle(&mut scheduler);
7815
7816        let process = scheduler.processes.get(&Pid(0)).unwrap();
7817        assert_eq!(process.registers[1], Value::Int(1)); // float is a float
7818        assert_eq!(process.registers[3], Value::Int(0)); // int is not a float
7819    }
7820
7821    #[test]
7822    fn test_int_to_float() {
7823        let mut scheduler = Scheduler::new();
7824
7825        let program = vec![
7826            Instruction::LoadInt {
7827                value: 42,
7828                dest: Register(0),
7829            },
7830            Instruction::IntToFloat {
7831                source: Register(0),
7832                dest: Register(1),
7833            },
7834            Instruction::End,
7835        ];
7836
7837        scheduler.spawn(program);
7838        run_to_idle(&mut scheduler);
7839
7840        let process = scheduler.processes.get(&Pid(0)).unwrap();
7841        assert_eq!(process.registers[1], Value::Float(42.0));
7842    }
7843
7844    #[test]
7845    fn test_float_to_int() {
7846        let mut scheduler = Scheduler::new();
7847
7848        let program = vec![
7849            Instruction::LoadFloat {
7850                value: 3.7,
7851                dest: Register(0),
7852            },
7853            Instruction::FloatToInt {
7854                source: Register(0),
7855                dest: Register(1),
7856            },
7857            Instruction::LoadFloat {
7858                value: -2.9,
7859                dest: Register(2),
7860            },
7861            Instruction::FloatToInt {
7862                source: Register(2),
7863                dest: Register(3),
7864            },
7865            Instruction::End,
7866        ];
7867
7868        scheduler.spawn(program);
7869        run_to_idle(&mut scheduler);
7870
7871        let process = scheduler.processes.get(&Pid(0)).unwrap();
7872        assert_eq!(process.registers[1], Value::Int(3)); // truncates toward zero
7873        assert_eq!(process.registers[3], Value::Int(-2)); // truncates toward zero
7874    }
7875
7876    #[test]
7877    fn test_floor_ceil_round_trunc() {
7878        let mut scheduler = Scheduler::new();
7879
7880        let program = vec![
7881            Instruction::LoadFloat {
7882                value: 3.7,
7883                dest: Register(0),
7884            },
7885            Instruction::Floor {
7886                source: Register(0),
7887                dest: Register(1),
7888            },
7889            Instruction::Ceil {
7890                source: Register(0),
7891                dest: Register(2),
7892            },
7893            Instruction::Round {
7894                source: Register(0),
7895                dest: Register(3),
7896            },
7897            Instruction::Trunc {
7898                source: Register(0),
7899                dest: Register(4),
7900            },
7901            Instruction::End,
7902        ];
7903
7904        scheduler.spawn(program);
7905        run_to_idle(&mut scheduler);
7906
7907        let process = scheduler.processes.get(&Pid(0)).unwrap();
7908        assert_eq!(process.registers[1], Value::Float(3.0)); // floor(3.7) = 3.0
7909        assert_eq!(process.registers[2], Value::Float(4.0)); // ceil(3.7) = 4.0
7910        assert_eq!(process.registers[3], Value::Float(4.0)); // round(3.7) = 4.0
7911        assert_eq!(process.registers[4], Value::Float(3.0)); // trunc(3.7) = 3.0
7912    }
7913
7914    #[test]
7915    fn test_sqrt() {
7916        let mut scheduler = Scheduler::new();
7917
7918        let program = vec![
7919            Instruction::LoadFloat {
7920                value: 16.0,
7921                dest: Register(0),
7922            },
7923            Instruction::Sqrt {
7924                source: Register(0),
7925                dest: Register(1),
7926            },
7927            // sqrt also works on int
7928            Instruction::LoadInt {
7929                value: 25,
7930                dest: Register(2),
7931            },
7932            Instruction::Sqrt {
7933                source: Register(2),
7934                dest: Register(3),
7935            },
7936            Instruction::End,
7937        ];
7938
7939        scheduler.spawn(program);
7940        run_to_idle(&mut scheduler);
7941
7942        let process = scheduler.processes.get(&Pid(0)).unwrap();
7943        assert_eq!(process.registers[1], Value::Float(4.0));
7944        assert_eq!(process.registers[3], Value::Float(5.0));
7945    }
7946
7947    #[test]
7948    fn test_abs() {
7949        let mut scheduler = Scheduler::new();
7950
7951        let program = vec![
7952            Instruction::LoadFloat {
7953                value: -3.5,
7954                dest: Register(0),
7955            },
7956            Instruction::Abs {
7957                source: Register(0),
7958                dest: Register(1),
7959            },
7960            Instruction::LoadInt {
7961                value: -42,
7962                dest: Register(2),
7963            },
7964            Instruction::Abs {
7965                source: Register(2),
7966                dest: Register(3),
7967            },
7968            Instruction::End,
7969        ];
7970
7971        scheduler.spawn(program);
7972        run_to_idle(&mut scheduler);
7973
7974        let process = scheduler.processes.get(&Pid(0)).unwrap();
7975        assert_eq!(process.registers[1], Value::Float(3.5));
7976        assert_eq!(process.registers[3], Value::Int(42));
7977    }
7978
7979    #[test]
7980    fn test_pow() {
7981        let mut scheduler = Scheduler::new();
7982
7983        let program = vec![
7984            // 2^3 = 8
7985            Instruction::LoadInt {
7986                value: 2,
7987                dest: Register(0),
7988            },
7989            Instruction::LoadInt {
7990                value: 3,
7991                dest: Register(1),
7992            },
7993            Instruction::Pow {
7994                base: Register(0),
7995                exp: Register(1),
7996                dest: Register(2),
7997            },
7998            // 2.5^2 = 6.25
7999            Instruction::LoadFloat {
8000                value: 2.5,
8001                dest: Register(3),
8002            },
8003            Instruction::LoadInt {
8004                value: 2,
8005                dest: Register(4),
8006            },
8007            Instruction::Pow {
8008                base: Register(3),
8009                exp: Register(4),
8010                dest: Register(5),
8011            },
8012            Instruction::End,
8013        ];
8014
8015        scheduler.spawn(program);
8016        run_to_idle(&mut scheduler);
8017
8018        let process = scheduler.processes.get(&Pid(0)).unwrap();
8019        assert_eq!(process.registers[2], Value::Float(8.0));
8020        assert_eq!(process.registers[5], Value::Float(6.25));
8021    }
8022
8023    // ========== Unlink/Demonitor Tests ==========
8024
8025    #[test]
8026    fn test_unlink() {
8027        let mut scheduler = Scheduler::new();
8028
8029        // Spawner creates a worker and links to it, then unlinks
8030        let worker = vec![
8031            Instruction::Work { amount: 100 },
8032            Instruction::Crash, // Worker will crash
8033        ];
8034
8035        let spawner = vec![
8036            Instruction::SpawnLink {
8037                code: worker,
8038                dest: Register(0),
8039            },
8040            // Unlink from the worker before it crashes
8041            Instruction::Unlink {
8042                target: Source::Reg(Register(0)),
8043            },
8044            // Wait a bit for worker to crash
8045            Instruction::Work { amount: 200 },
8046            Instruction::End,
8047        ];
8048
8049        scheduler.spawn(spawner);
8050        run_to_idle(&mut scheduler);
8051
8052        // Spawner should have completed normally (not crashed with worker)
8053        let spawner_process = scheduler.processes.get(&Pid(0)).unwrap();
8054        assert_eq!(spawner_process.status, ProcessStatus::Done);
8055
8056        // Worker should have crashed
8057        let worker_process = scheduler.processes.get(&Pid(1)).unwrap();
8058        assert_eq!(worker_process.status, ProcessStatus::Crashed);
8059    }
8060
8061    #[test]
8062    fn test_monitor_returns_ref() {
8063        let mut scheduler = Scheduler::new();
8064
8065        let worker = vec![Instruction::End];
8066
8067        let observer = vec![
8068            Instruction::Spawn {
8069                code: worker,
8070                dest: Register(0),
8071            },
8072            Instruction::Monitor {
8073                target: Source::Reg(Register(0)),
8074                dest: Register(1),
8075            },
8076            Instruction::End,
8077        ];
8078
8079        scheduler.spawn(observer);
8080        run_to_idle(&mut scheduler);
8081
8082        let process = scheduler.processes.get(&Pid(0)).unwrap();
8083        // Register 1 should contain a Ref
8084        match &process.registers[1] {
8085            Value::Ref(_) => {}
8086            other => panic!("Expected Ref, got {:?}", other),
8087        }
8088    }
8089
8090    #[test]
8091    fn test_demonitor() {
8092        let mut scheduler = Scheduler::new();
8093
8094        // Worker will crash
8095        let worker = vec![Instruction::Work { amount: 100 }, Instruction::Crash];
8096
8097        let observer = vec![
8098            Instruction::Spawn {
8099                code: worker,
8100                dest: Register(0),
8101            },
8102            // Monitor the worker
8103            Instruction::Monitor {
8104                target: Source::Reg(Register(0)),
8105                dest: Register(1),
8106            },
8107            // Immediately demonitor
8108            Instruction::Demonitor {
8109                monitor_ref: Register(1),
8110            },
8111            // Try to receive - should timeout since we demonitored
8112            Instruction::ReceiveTimeout {
8113                dest: Register(2),
8114                timeout: 50,
8115            },
8116            Instruction::End,
8117        ];
8118
8119        scheduler.spawn(observer);
8120        run_to_idle(&mut scheduler);
8121
8122        // Observer should complete (not receive DOWN message since we demonitored)
8123        let process = scheduler.processes.get(&Pid(0)).unwrap();
8124        assert_eq!(process.status, ProcessStatus::Done);
8125        // Should have received TIMEOUT, not DOWN
8126        assert_eq!(process.registers[2], Value::String("TIMEOUT".to_string()));
8127    }
8128
8129    #[test]
8130    fn test_multiple_monitors_same_target() {
8131        let mut scheduler = Scheduler::new();
8132
8133        let worker = vec![Instruction::End];
8134
8135        let observer = vec![
8136            Instruction::Spawn {
8137                code: worker,
8138                dest: Register(0),
8139            },
8140            // Set up two monitors on the same target
8141            Instruction::Monitor {
8142                target: Source::Reg(Register(0)),
8143                dest: Register(1),
8144            },
8145            Instruction::Monitor {
8146                target: Source::Reg(Register(0)),
8147                dest: Register(2),
8148            },
8149            Instruction::End,
8150        ];
8151
8152        scheduler.spawn(observer);
8153        run_to_idle(&mut scheduler);
8154
8155        let process = scheduler.processes.get(&Pid(0)).unwrap();
8156
8157        // Both should be Refs with different values
8158        match (&process.registers[1], &process.registers[2]) {
8159            (Value::Ref(r1), Value::Ref(r2)) => {
8160                assert_ne!(r1, r2); // Different monitor refs
8161            }
8162            _ => panic!("Expected two Refs"),
8163        }
8164    }
8165
8166    #[test]
8167    fn test_exit_with_normal_reason() {
8168        // Normal exit doesn't propagate to linked processes (unless they trap_exit)
8169        let mut scheduler = Scheduler::new();
8170
8171        // Child: exit normally
8172        let child = vec![
8173            Instruction::LoadAtom {
8174                name: "normal".to_string(),
8175                dest: Register(0),
8176            },
8177            Instruction::Exit {
8178                reason: Register(0),
8179            },
8180        ];
8181
8182        // Parent: link to child, wait for message (should not receive one)
8183        let parent = vec![
8184            Instruction::SpawnLink {
8185                code: child,
8186                dest: Register(0),
8187            },
8188            // Use timeout to avoid blocking forever
8189            Instruction::ReceiveTimeout {
8190                dest: Register(1),
8191                timeout: 10,
8192            },
8193            Instruction::End,
8194        ];
8195
8196        scheduler.spawn(parent);
8197        run_to_idle(&mut scheduler);
8198
8199        // Parent should finish normally (child's normal exit doesn't propagate)
8200        let parent_process = scheduler.processes.get(&Pid(0)).unwrap();
8201        assert_eq!(parent_process.status, ProcessStatus::Done);
8202        // Should have timed out, not received an exit message
8203        assert_eq!(
8204            parent_process.registers[1],
8205            Value::String("TIMEOUT".to_string())
8206        );
8207    }
8208
8209    #[test]
8210    fn test_exit_with_custom_reason() {
8211        // Exit with a custom reason propagates through trap_exit
8212        let mut scheduler = Scheduler::new();
8213
8214        // Child: exit with custom reason
8215        let child = vec![
8216            Instruction::LoadAtom {
8217                name: "shutdown".to_string(),
8218                dest: Register(0),
8219            },
8220            Instruction::Exit {
8221                reason: Register(0),
8222            },
8223        ];
8224
8225        // Parent: trap_exit, link to child, receive exit message
8226        let parent = vec![
8227            Instruction::TrapExit { enable: true },
8228            Instruction::SpawnLink {
8229                code: child,
8230                dest: Register(0),
8231            },
8232            Instruction::Receive { dest: Register(1) },
8233            Instruction::End,
8234        ];
8235
8236        scheduler.spawn(parent);
8237        run_to_idle(&mut scheduler);
8238
8239        // Parent should have received {:EXIT, Pid, :shutdown}
8240        let parent_process = scheduler.processes.get(&Pid(0)).unwrap();
8241        assert_eq!(parent_process.status, ProcessStatus::Done);
8242        match &parent_process.registers[1] {
8243            Value::Tuple(elems) => {
8244                assert_eq!(elems.len(), 3);
8245                assert_eq!(elems[0], Value::Atom("EXIT".to_string()));
8246                assert!(matches!(elems[1], Value::Pid(_)));
8247                assert_eq!(elems[2], Value::Atom("shutdown".to_string()));
8248            }
8249            _ => panic!("Expected EXIT tuple, got {:?}", parent_process.registers[1]),
8250        }
8251    }
8252
8253    #[test]
8254    fn test_link_crash_without_trap_exit() {
8255        // Without trap_exit, linked process crash propagates (crashes parent too)
8256        let mut scheduler = Scheduler::new();
8257
8258        // Child: crash immediately
8259        let child = vec![Instruction::Crash];
8260
8261        // Parent: link to child, try to receive (should crash before receiving)
8262        let parent = vec![
8263            Instruction::SpawnLink {
8264                code: child,
8265                dest: Register(0),
8266            },
8267            Instruction::Receive { dest: Register(1) },
8268            Instruction::End,
8269        ];
8270
8271        scheduler.spawn(parent);
8272        run_to_idle(&mut scheduler);
8273
8274        // Parent should have crashed (exit signal propagated)
8275        let parent_process = scheduler.processes.get(&Pid(0)).unwrap();
8276        assert_eq!(parent_process.status, ProcessStatus::Crashed);
8277        assert_eq!(
8278            parent_process.exit_reason,
8279            Value::Atom("crashed".to_string())
8280        );
8281    }
8282
8283    #[test]
8284    fn test_trap_exit_toggle() {
8285        // Test that TrapExit instruction works
8286        let mut scheduler = Scheduler::new();
8287
8288        // Worker exits normally
8289        let worker = vec![Instruction::End];
8290
8291        // Process: enable trap_exit, spawn_link worker, verify trap_exit is enabled
8292        let program = vec![
8293            Instruction::TrapExit { enable: true },
8294            Instruction::SpawnLink {
8295                code: worker,
8296                dest: Register(0),
8297            },
8298            // Use timeout to not block forever
8299            Instruction::ReceiveTimeout {
8300                dest: Register(1),
8301                timeout: 10,
8302            },
8303            Instruction::End,
8304        ];
8305
8306        scheduler.spawn(program);
8307        run_to_idle(&mut scheduler);
8308
8309        // Process should have finished and have trap_exit enabled
8310        let process = scheduler.processes.get(&Pid(0)).unwrap();
8311        assert!(process.trap_exit);
8312        assert_eq!(process.status, ProcessStatus::Done);
8313    }
8314
8315    #[test]
8316    fn test_exit_reason_stored_in_process() {
8317        // Test that exit reason is stored in the process
8318        let mut scheduler = Scheduler::new();
8319
8320        let program = vec![
8321            Instruction::LoadAtom {
8322                name: "my_reason".to_string(),
8323                dest: Register(0),
8324            },
8325            Instruction::Exit {
8326                reason: Register(0),
8327            },
8328        ];
8329
8330        scheduler.spawn(program);
8331        run_to_idle(&mut scheduler);
8332
8333        let process = scheduler.processes.get(&Pid(0)).unwrap();
8334        assert_eq!(process.status, ProcessStatus::Crashed); // not :normal
8335        assert_eq!(process.exit_reason, Value::Atom("my_reason".to_string()));
8336    }
8337
8338    #[test]
8339    fn test_monitor_receives_exit_reason() {
8340        // Monitor DOWN message includes the exit reason
8341        let mut scheduler = Scheduler::new();
8342
8343        // Worker: exit with custom reason
8344        let worker = vec![
8345            Instruction::LoadAtom {
8346                name: "killed".to_string(),
8347                dest: Register(0),
8348            },
8349            Instruction::Exit {
8350                reason: Register(0),
8351            },
8352        ];
8353
8354        // Observer: spawn, monitor, wait for DOWN
8355        let observer = vec![
8356            Instruction::Spawn {
8357                code: worker,
8358                dest: Register(0),
8359            },
8360            Instruction::Monitor {
8361                target: Source::Reg(Register(0)),
8362                dest: Register(2),
8363            },
8364            Instruction::Receive { dest: Register(1) },
8365            Instruction::End,
8366        ];
8367
8368        scheduler.spawn(observer);
8369        run_to_idle(&mut scheduler);
8370
8371        // Observer should have received {:DOWN, Ref, :process, Pid, :killed}
8372        let observer_process = scheduler.processes.get(&Pid(0)).unwrap();
8373        match &observer_process.registers[1] {
8374            Value::Tuple(elems) => {
8375                assert_eq!(elems.len(), 5);
8376                assert_eq!(elems[0], Value::Atom("DOWN".to_string()));
8377                assert!(matches!(elems[1], Value::Ref(_)));
8378                assert_eq!(elems[2], Value::Atom("process".to_string()));
8379                assert!(matches!(elems[3], Value::Pid(_)));
8380                assert_eq!(elems[4], Value::Atom("killed".to_string()));
8381            }
8382            _ => panic!(
8383                "Expected DOWN tuple, got {:?}",
8384                observer_process.registers[1]
8385            ),
8386        }
8387    }
8388
8389    // ========== Timer Tests ==========
8390
8391    #[test]
8392    fn test_start_timer() {
8393        // StartTimer sends {:timeout, ref, msg} to self after delay
8394        let mut scheduler = Scheduler::new();
8395
8396        let program = vec![
8397            // Load message value
8398            Instruction::LoadAtom {
8399                name: "ping".to_string(),
8400                dest: Register(0),
8401            },
8402            // Start timer with 5 reduction delay
8403            Instruction::StartTimer {
8404                delay: 5,
8405                msg: Register(0),
8406                dest: Register(1),
8407            },
8408            // Wait for the timer message
8409            Instruction::Receive { dest: Register(2) },
8410            Instruction::End,
8411        ];
8412
8413        scheduler.spawn(program);
8414        run_to_idle(&mut scheduler);
8415
8416        let process = scheduler.processes.get(&Pid(0)).unwrap();
8417        assert_eq!(process.status, ProcessStatus::Done);
8418
8419        // R1 should have the timer ref
8420        assert!(matches!(process.registers[1], Value::Ref(_)));
8421
8422        // R2 should have received the timeout tuple (as string from debug format)
8423        match &process.registers[2] {
8424            Value::String(s) => {
8425                assert!(s.contains("timeout"));
8426                assert!(s.contains("ping"));
8427            }
8428            _ => panic!(
8429                "Expected timeout message string, got {:?}",
8430                process.registers[2]
8431            ),
8432        }
8433    }
8434
8435    #[test]
8436    fn test_send_after() {
8437        // SendAfter sends a message to another process after delay
8438        let mut scheduler = Scheduler::new();
8439
8440        // Receiver waits for message
8441        let receiver = vec![Instruction::Receive { dest: Register(0) }, Instruction::End];
8442
8443        // Sender spawns receiver, then sends after delay
8444        let sender = vec![
8445            Instruction::Spawn {
8446                code: receiver,
8447                dest: Register(0),
8448            },
8449            // Load message
8450            Instruction::LoadAtom {
8451                name: "hello".to_string(),
8452                dest: Register(1),
8453            },
8454            // Send after 5 reductions
8455            Instruction::SendAfter {
8456                delay: 5,
8457                to: Source::Reg(Register(0)),
8458                msg: Register(1),
8459                dest: Register(2),
8460            },
8461            // Wait a bit for timer to fire
8462            Instruction::Work { amount: 10 },
8463            Instruction::End,
8464        ];
8465
8466        scheduler.spawn(sender);
8467        run_to_idle(&mut scheduler);
8468
8469        // Check receiver got the message
8470        let receiver_process = scheduler.processes.get(&Pid(1)).unwrap();
8471        assert_eq!(receiver_process.status, ProcessStatus::Done);
8472        match &receiver_process.registers[0] {
8473            Value::String(s) => assert!(s.contains("hello")),
8474            _ => panic!("Expected message, got {:?}", receiver_process.registers[0]),
8475        }
8476    }
8477
8478    #[test]
8479    fn test_cancel_timer() {
8480        // CancelTimer stops a pending timer
8481        let mut scheduler = Scheduler::new();
8482
8483        let program = vec![
8484            // Load message
8485            Instruction::LoadAtom {
8486                name: "ping".to_string(),
8487                dest: Register(0),
8488            },
8489            // Start timer with long delay
8490            Instruction::StartTimer {
8491                delay: 1000,
8492                msg: Register(0),
8493                dest: Register(1),
8494            },
8495            // Cancel it immediately
8496            Instruction::CancelTimer {
8497                timer_ref: Register(1),
8498                dest: Register(2),
8499            },
8500            // Try to receive with timeout (should timeout, not receive timer msg)
8501            Instruction::ReceiveTimeout {
8502                dest: Register(3),
8503                timeout: 10,
8504            },
8505            Instruction::End,
8506        ];
8507
8508        scheduler.spawn(program);
8509        run_to_idle(&mut scheduler);
8510
8511        let process = scheduler.processes.get(&Pid(0)).unwrap();
8512        assert_eq!(process.status, ProcessStatus::Done);
8513
8514        // R2 should have remaining time (close to 1000)
8515        match &process.registers[2] {
8516            Value::Int(remaining) => assert!(*remaining > 900),
8517            _ => panic!("Expected remaining time, got {:?}", process.registers[2]),
8518        }
8519
8520        // R3 should have TIMEOUT (timer was cancelled)
8521        assert_eq!(process.registers[3], Value::String("TIMEOUT".to_string()));
8522
8523        // Timer queue should be empty
8524        assert!(scheduler.timers.is_empty());
8525    }
8526
8527    #[test]
8528    fn test_read_timer() {
8529        // ReadTimer returns remaining time
8530        let mut scheduler = Scheduler::new();
8531
8532        let program = vec![
8533            Instruction::LoadAtom {
8534                name: "ping".to_string(),
8535                dest: Register(0),
8536            },
8537            Instruction::StartTimer {
8538                delay: 100,
8539                msg: Register(0),
8540                dest: Register(1),
8541            },
8542            // Read timer immediately
8543            Instruction::ReadTimer {
8544                timer_ref: Register(1),
8545                dest: Register(2),
8546            },
8547            Instruction::End,
8548        ];
8549
8550        scheduler.spawn(program);
8551        run_to_idle(&mut scheduler);
8552
8553        let process = scheduler.processes.get(&Pid(0)).unwrap();
8554
8555        // R2 should have remaining time (may be slightly less due to processing)
8556        match &process.registers[2] {
8557            Value::Int(remaining) => assert!(*remaining > 0 && *remaining <= 100),
8558            _ => panic!("Expected remaining time, got {:?}", process.registers[2]),
8559        }
8560    }
8561
8562    #[test]
8563    fn test_timer_fires_after_delay() {
8564        // Verify timer fires after specified delay
8565        let mut scheduler = Scheduler::new();
8566
8567        let program = vec![
8568            Instruction::LoadAtom {
8569                name: "tick".to_string(),
8570                dest: Register(0),
8571            },
8572            // Start timer with delay
8573            Instruction::StartTimer {
8574                delay: 10,
8575                msg: Register(0),
8576                dest: Register(1),
8577            },
8578            // Do enough work that timer fires
8579            Instruction::Work { amount: 20 },
8580            // Receive the timer message (should be available now)
8581            Instruction::ReceiveTimeout {
8582                dest: Register(2),
8583                timeout: 5,
8584            },
8585            Instruction::End,
8586        ];
8587
8588        scheduler.spawn(program);
8589        run_to_idle(&mut scheduler);
8590
8591        let process = scheduler.processes.get(&Pid(0)).unwrap();
8592        assert_eq!(process.status, ProcessStatus::Done);
8593
8594        // R2 should have the timer message (not TIMEOUT)
8595        match &process.registers[2] {
8596            Value::String(s) => {
8597                assert!(s.contains("timeout") && s.contains("tick"));
8598            }
8599            _ => panic!("Expected timer message, got {:?}", process.registers[2]),
8600        }
8601    }
8602
8603    #[test]
8604    fn test_multiple_timers() {
8605        // Multiple timers fire in order
8606        let mut scheduler = Scheduler::new();
8607
8608        let program = vec![
8609            // Create three timers with different delays
8610            Instruction::LoadInt {
8611                value: 1,
8612                dest: Register(0),
8613            },
8614            Instruction::StartTimer {
8615                delay: 10,
8616                msg: Register(0),
8617                dest: Register(4),
8618            },
8619            Instruction::LoadInt {
8620                value: 2,
8621                dest: Register(0),
8622            },
8623            Instruction::StartTimer {
8624                delay: 20,
8625                msg: Register(0),
8626                dest: Register(5),
8627            },
8628            Instruction::LoadInt {
8629                value: 3,
8630                dest: Register(0),
8631            },
8632            Instruction::StartTimer {
8633                delay: 30,
8634                msg: Register(0),
8635                dest: Register(6),
8636            },
8637            // Wait for all timers
8638            Instruction::Work { amount: 50 },
8639            // Receive all three messages
8640            Instruction::Receive { dest: Register(1) },
8641            Instruction::Receive { dest: Register(2) },
8642            Instruction::Receive { dest: Register(3) },
8643            Instruction::End,
8644        ];
8645
8646        scheduler.spawn(program);
8647        run_to_idle(&mut scheduler);
8648
8649        let process = scheduler.processes.get(&Pid(0)).unwrap();
8650        assert_eq!(process.status, ProcessStatus::Done);
8651
8652        // All three timer refs should be present
8653        assert!(matches!(process.registers[4], Value::Ref(_)));
8654        assert!(matches!(process.registers[5], Value::Ref(_)));
8655        assert!(matches!(process.registers[6], Value::Ref(_)));
8656    }
8657
8658    // ========== Try/Catch/After Tests ==========
8659
8660    #[test]
8661    fn test_try_catch_basic() {
8662        // Basic try/catch - throw is caught
8663        let mut scheduler = Scheduler::new();
8664
8665        // try { throw(:error, :oops) } catch { handle }
8666        let program = vec![
8667            // 0: Try with catch at 5
8668            Instruction::Try {
8669                catch_target: 5,
8670                after_target: None,
8671            },
8672            // 1: Load :error class
8673            Instruction::LoadAtom {
8674                name: "error".to_string(),
8675                dest: Register(0),
8676            },
8677            // 2: Load :oops reason
8678            Instruction::LoadAtom {
8679                name: "oops".to_string(),
8680                dest: Register(1),
8681            },
8682            // 3: Throw
8683            Instruction::Throw {
8684                class: Register(0),
8685                reason: Register(1),
8686            },
8687            // 4: Should not reach here
8688            Instruction::LoadInt {
8689                value: 999,
8690                dest: Register(2),
8691            },
8692            // 5: Catch handler - get exception
8693            Instruction::GetException { dest: Register(3) },
8694            // 6: Clear exception
8695            Instruction::ClearException,
8696            // 7: Mark that we caught it
8697            Instruction::LoadAtom {
8698                name: "caught".to_string(),
8699                dest: Register(4),
8700            },
8701            // 8: End
8702            Instruction::End,
8703        ];
8704
8705        scheduler.spawn(program);
8706        run_to_idle(&mut scheduler);
8707
8708        let process = scheduler.processes.get(&Pid(0)).unwrap();
8709        assert_eq!(process.status, ProcessStatus::Done);
8710
8711        // R2 should NOT be 999 (we skipped that instruction)
8712        assert_ne!(process.registers[2], Value::Int(999));
8713
8714        // R4 should be :caught
8715        assert_eq!(process.registers[4], Value::Atom("caught".to_string()));
8716
8717        // R3 should have the exception tuple {class, reason, stacktrace}
8718        match &process.registers[3] {
8719            Value::Tuple(elems) => {
8720                assert_eq!(elems.len(), 3);
8721                assert_eq!(elems[0], Value::Atom("error".to_string()));
8722                assert_eq!(elems[1], Value::Atom("oops".to_string()));
8723            }
8724            _ => panic!("Expected exception tuple, got {:?}", process.registers[3]),
8725        }
8726    }
8727
8728    #[test]
8729    fn test_try_no_exception() {
8730        // Try block completes without exception
8731        let mut scheduler = Scheduler::new();
8732
8733        let program = vec![
8734            // 0: Try with catch at 4
8735            Instruction::Try {
8736                catch_target: 4,
8737                after_target: None,
8738            },
8739            // 1: Normal work (no throw)
8740            Instruction::LoadInt {
8741                value: 42,
8742                dest: Register(0),
8743            },
8744            // 2: EndTry
8745            Instruction::EndTry,
8746            // 3: Jump to end (skip catch)
8747            Instruction::Jump { target: 6 },
8748            // 4: Catch handler (should not run)
8749            Instruction::LoadAtom {
8750                name: "caught".to_string(),
8751                dest: Register(1),
8752            },
8753            // 5: Jump to end
8754            Instruction::Jump { target: 6 },
8755            // 6: End
8756            Instruction::End,
8757        ];
8758
8759        scheduler.spawn(program);
8760        run_to_idle(&mut scheduler);
8761
8762        let process = scheduler.processes.get(&Pid(0)).unwrap();
8763        assert_eq!(process.status, ProcessStatus::Done);
8764
8765        // R0 should be 42
8766        assert_eq!(process.registers[0], Value::Int(42));
8767
8768        // R1 should NOT be :caught (catch didn't run)
8769        assert_ne!(process.registers[1], Value::Atom("caught".to_string()));
8770    }
8771
8772    #[test]
8773    fn test_throw_without_catch() {
8774        // Throw without handler crashes process
8775        let mut scheduler = Scheduler::new();
8776
8777        let program = vec![
8778            Instruction::LoadAtom {
8779                name: "throw".to_string(),
8780                dest: Register(0),
8781            },
8782            Instruction::LoadAtom {
8783                name: "uncaught".to_string(),
8784                dest: Register(1),
8785            },
8786            Instruction::Throw {
8787                class: Register(0),
8788                reason: Register(1),
8789            },
8790            Instruction::End,
8791        ];
8792
8793        scheduler.spawn(program);
8794        run_to_idle(&mut scheduler);
8795
8796        let process = scheduler.processes.get(&Pid(0)).unwrap();
8797        assert_eq!(process.status, ProcessStatus::Crashed);
8798
8799        // Exit reason should be tuple of {class, reason}
8800        match &process.exit_reason {
8801            Value::Tuple(elems) => {
8802                assert_eq!(elems.len(), 2);
8803                assert_eq!(elems[0], Value::Atom("throw".to_string()));
8804                assert_eq!(elems[1], Value::Atom("uncaught".to_string()));
8805            }
8806            _ => panic!("Expected exit reason tuple, got {:?}", process.exit_reason),
8807        }
8808    }
8809
8810    #[test]
8811    fn test_try_with_after() {
8812        // After block runs on normal completion
8813        let mut scheduler = Scheduler::new();
8814
8815        let program = vec![
8816            // 0: Try with catch at 4, after at 6
8817            Instruction::Try {
8818                catch_target: 4,
8819                after_target: Some(6),
8820            },
8821            // 1: Normal work
8822            Instruction::LoadInt {
8823                value: 1,
8824                dest: Register(0),
8825            },
8826            // 2: EndTry (jumps to after)
8827            Instruction::EndTry,
8828            // 3: Should not reach (EndTry jumps to after)
8829            Instruction::LoadInt {
8830                value: 999,
8831                dest: Register(3),
8832            },
8833            // 4: Catch handler
8834            Instruction::LoadAtom {
8835                name: "caught".to_string(),
8836                dest: Register(1),
8837            },
8838            // 5: Jump to after (from catch)
8839            Instruction::Jump { target: 6 },
8840            // 6: After block (cleanup)
8841            Instruction::LoadAtom {
8842                name: "cleanup".to_string(),
8843                dest: Register(2),
8844            },
8845            // 7: End
8846            Instruction::End,
8847        ];
8848
8849        scheduler.spawn(program);
8850        run_to_idle(&mut scheduler);
8851
8852        let process = scheduler.processes.get(&Pid(0)).unwrap();
8853        assert_eq!(process.status, ProcessStatus::Done);
8854
8855        // R0 should be 1 (try block ran)
8856        assert_eq!(process.registers[0], Value::Int(1));
8857
8858        // R1 should NOT be :caught (no exception)
8859        assert_ne!(process.registers[1], Value::Atom("caught".to_string()));
8860
8861        // R2 should be :cleanup (after ran)
8862        assert_eq!(process.registers[2], Value::Atom("cleanup".to_string()));
8863
8864        // R3 should NOT be 999 (we jumped over it)
8865        assert_ne!(process.registers[3], Value::Int(999));
8866    }
8867
8868    #[test]
8869    fn test_reraise() {
8870        // Catch and re-raise exception
8871        let mut scheduler = Scheduler::new();
8872
8873        let program = vec![
8874            // 0: Outer try with catch at 9
8875            Instruction::Try {
8876                catch_target: 9,
8877                after_target: None,
8878            },
8879            // 1: Inner try with catch at 6
8880            Instruction::Try {
8881                catch_target: 6,
8882                after_target: None,
8883            },
8884            // 2: Load class
8885            Instruction::LoadAtom {
8886                name: "error".to_string(),
8887                dest: Register(0),
8888            },
8889            // 3: Load reason
8890            Instruction::LoadAtom {
8891                name: "inner".to_string(),
8892                dest: Register(1),
8893            },
8894            // 4: Throw
8895            Instruction::Throw {
8896                class: Register(0),
8897                reason: Register(1),
8898            },
8899            // 5: Should not reach
8900            Instruction::End,
8901            // 6: Inner catch - mark and reraise
8902            Instruction::LoadAtom {
8903                name: "inner_caught".to_string(),
8904                dest: Register(2),
8905            },
8906            // 7: Reraise
8907            Instruction::Reraise,
8908            // 8: Should not reach
8909            Instruction::End,
8910            // 9: Outer catch
8911            Instruction::LoadAtom {
8912                name: "outer_caught".to_string(),
8913                dest: Register(3),
8914            },
8915            // 10: End
8916            Instruction::End,
8917        ];
8918
8919        scheduler.spawn(program);
8920        run_to_idle(&mut scheduler);
8921
8922        let process = scheduler.processes.get(&Pid(0)).unwrap();
8923        assert_eq!(process.status, ProcessStatus::Done);
8924
8925        // Both catches should have run
8926        assert_eq!(
8927            process.registers[2],
8928            Value::Atom("inner_caught".to_string())
8929        );
8930        assert_eq!(
8931            process.registers[3],
8932            Value::Atom("outer_caught".to_string())
8933        );
8934    }
8935
8936    #[test]
8937    fn test_nested_try() {
8938        // Nested try blocks
8939        let mut scheduler = Scheduler::new();
8940
8941        let program = vec![
8942            // 0: Outer try
8943            Instruction::Try {
8944                catch_target: 7,
8945                after_target: None,
8946            },
8947            // 1: Inner try
8948            Instruction::Try {
8949                catch_target: 5,
8950                after_target: None,
8951            },
8952            // 2: Throw from inner
8953            Instruction::LoadAtom {
8954                name: "error".to_string(),
8955                dest: Register(0),
8956            },
8957            Instruction::LoadAtom {
8958                name: "inner_error".to_string(),
8959                dest: Register(1),
8960            },
8961            Instruction::Throw {
8962                class: Register(0),
8963                reason: Register(1),
8964            },
8965            // 5: Inner catch - handle it
8966            Instruction::LoadAtom {
8967                name: "handled".to_string(),
8968                dest: Register(2),
8969            },
8970            Instruction::ClearException,
8971            // 7: This is outer catch (but we won't reach it if inner handles)
8972            // Actually, since inner handled, we need to EndTry and continue
8973            // Let me fix this test structure...
8974            Instruction::End,
8975        ];
8976
8977        scheduler.spawn(program);
8978        run_to_idle(&mut scheduler);
8979
8980        let process = scheduler.processes.get(&Pid(0)).unwrap();
8981        assert_eq!(process.status, ProcessStatus::Done);
8982
8983        // Inner catch handled it
8984        assert_eq!(process.registers[2], Value::Atom("handled".to_string()));
8985    }
8986
8987    // ========== Binary Tests ==========
8988
8989    #[test]
8990    fn test_make_binary() {
8991        let mut scheduler = Scheduler::new();
8992
8993        let program = vec![
8994            Instruction::MakeBinary {
8995                bytes: vec![1, 2, 3, 255],
8996                dest: Register(0),
8997            },
8998            Instruction::End,
8999        ];
9000
9001        scheduler.spawn(program);
9002        run_to_idle(&mut scheduler);
9003
9004        let process = scheduler.processes.get(&Pid(0)).unwrap();
9005        assert_eq!(process.registers[0], Value::Binary(vec![1, 2, 3, 255]));
9006    }
9007
9008    #[test]
9009    fn test_binary_size() {
9010        let mut scheduler = Scheduler::new();
9011
9012        let program = vec![
9013            Instruction::MakeBinary {
9014                bytes: vec![1, 2, 3, 4, 5],
9015                dest: Register(0),
9016            },
9017            Instruction::BinarySize {
9018                bin: Register(0),
9019                dest: Register(1),
9020            },
9021            Instruction::End,
9022        ];
9023
9024        scheduler.spawn(program);
9025        run_to_idle(&mut scheduler);
9026
9027        let process = scheduler.processes.get(&Pid(0)).unwrap();
9028        assert_eq!(process.registers[1], Value::Int(5));
9029    }
9030
9031    #[test]
9032    fn test_binary_at() {
9033        let mut scheduler = Scheduler::new();
9034
9035        let program = vec![
9036            Instruction::MakeBinary {
9037                bytes: vec![10, 20, 30, 40],
9038                dest: Register(0),
9039            },
9040            Instruction::LoadInt {
9041                value: 2,
9042                dest: Register(1),
9043            },
9044            Instruction::BinaryAt {
9045                bin: Register(0),
9046                index: Register(1),
9047                dest: Register(2),
9048            },
9049            Instruction::End,
9050        ];
9051
9052        scheduler.spawn(program);
9053        run_to_idle(&mut scheduler);
9054
9055        let process = scheduler.processes.get(&Pid(0)).unwrap();
9056        assert_eq!(process.registers[2], Value::Int(30)); // byte at index 2
9057    }
9058
9059    #[test]
9060    fn test_binary_at_out_of_bounds() {
9061        let mut scheduler = Scheduler::new();
9062
9063        let program = vec![
9064            Instruction::MakeBinary {
9065                bytes: vec![1, 2, 3],
9066                dest: Register(0),
9067            },
9068            Instruction::LoadInt {
9069                value: 10, // out of bounds
9070                dest: Register(1),
9071            },
9072            Instruction::BinaryAt {
9073                bin: Register(0),
9074                index: Register(1),
9075                dest: Register(2),
9076            },
9077            Instruction::End,
9078        ];
9079
9080        scheduler.spawn(program);
9081        run_to_idle(&mut scheduler);
9082
9083        let process = scheduler.processes.get(&Pid(0)).unwrap();
9084        assert_eq!(process.status, ProcessStatus::Crashed);
9085    }
9086
9087    #[test]
9088    fn test_binary_slice() {
9089        let mut scheduler = Scheduler::new();
9090
9091        let program = vec![
9092            Instruction::MakeBinary {
9093                bytes: vec![0, 1, 2, 3, 4, 5],
9094                dest: Register(0),
9095            },
9096            Instruction::LoadInt {
9097                value: 2, // start
9098                dest: Register(1),
9099            },
9100            Instruction::LoadInt {
9101                value: 3, // length
9102                dest: Register(2),
9103            },
9104            Instruction::BinarySlice {
9105                bin: Register(0),
9106                start: Register(1),
9107                len: Register(2),
9108                dest: Register(3),
9109            },
9110            Instruction::End,
9111        ];
9112
9113        scheduler.spawn(program);
9114        run_to_idle(&mut scheduler);
9115
9116        let process = scheduler.processes.get(&Pid(0)).unwrap();
9117        assert_eq!(process.registers[3], Value::Binary(vec![2, 3, 4]));
9118    }
9119
9120    #[test]
9121    fn test_binary_concat() {
9122        let mut scheduler = Scheduler::new();
9123
9124        let program = vec![
9125            Instruction::MakeBinary {
9126                bytes: vec![1, 2, 3],
9127                dest: Register(0),
9128            },
9129            Instruction::MakeBinary {
9130                bytes: vec![4, 5],
9131                dest: Register(1),
9132            },
9133            Instruction::BinaryConcat {
9134                a: Register(0),
9135                b: Register(1),
9136                dest: Register(2),
9137            },
9138            Instruction::End,
9139        ];
9140
9141        scheduler.spawn(program);
9142        run_to_idle(&mut scheduler);
9143
9144        let process = scheduler.processes.get(&Pid(0)).unwrap();
9145        assert_eq!(process.registers[2], Value::Binary(vec![1, 2, 3, 4, 5]));
9146    }
9147
9148    #[test]
9149    fn test_is_binary() {
9150        let mut scheduler = Scheduler::new();
9151
9152        let program = vec![
9153            Instruction::MakeBinary {
9154                bytes: vec![1, 2, 3],
9155                dest: Register(0),
9156            },
9157            Instruction::IsBinary {
9158                source: Register(0),
9159                dest: Register(1),
9160            },
9161            Instruction::LoadInt {
9162                value: 42,
9163                dest: Register(2),
9164            },
9165            Instruction::IsBinary {
9166                source: Register(2),
9167                dest: Register(3),
9168            },
9169            Instruction::End,
9170        ];
9171
9172        scheduler.spawn(program);
9173        run_to_idle(&mut scheduler);
9174
9175        let process = scheduler.processes.get(&Pid(0)).unwrap();
9176        assert_eq!(process.registers[1], Value::Int(1)); // binary is binary
9177        assert_eq!(process.registers[3], Value::Int(0)); // int is not binary
9178    }
9179
9180    #[test]
9181    fn test_binary_to_string() {
9182        let mut scheduler = Scheduler::new();
9183
9184        let program = vec![
9185            Instruction::MakeBinary {
9186                bytes: "hello".as_bytes().to_vec(),
9187                dest: Register(0),
9188            },
9189            Instruction::BinaryToString {
9190                source: Register(0),
9191                dest: Register(1),
9192            },
9193            Instruction::End,
9194        ];
9195
9196        scheduler.spawn(program);
9197        run_to_idle(&mut scheduler);
9198
9199        let process = scheduler.processes.get(&Pid(0)).unwrap();
9200        assert_eq!(process.registers[1], Value::String("hello".to_string()));
9201    }
9202
9203    #[test]
9204    fn test_binary_to_string_invalid_utf8() {
9205        let mut scheduler = Scheduler::new();
9206
9207        let program = vec![
9208            Instruction::MakeBinary {
9209                bytes: vec![0xFF, 0xFE], // Invalid UTF-8
9210                dest: Register(0),
9211            },
9212            Instruction::BinaryToString {
9213                source: Register(0),
9214                dest: Register(1),
9215            },
9216            Instruction::End,
9217        ];
9218
9219        scheduler.spawn(program);
9220        run_to_idle(&mut scheduler);
9221
9222        let process = scheduler.processes.get(&Pid(0)).unwrap();
9223        assert_eq!(process.status, ProcessStatus::Crashed);
9224    }
9225
9226    #[test]
9227    fn test_binary_pattern_matching() {
9228        let mut scheduler = Scheduler::new();
9229
9230        let program = vec![
9231            // Create binary <<1, 2, 3>>
9232            Instruction::MakeBinary {
9233                bytes: vec![1, 2, 3],
9234                dest: Register(0),
9235            },
9236            // Try to match it against <<1, 2, 3>>
9237            Instruction::Match {
9238                source: Register(0),
9239                pattern: Pattern::Binary(vec![1, 2, 3]),
9240                fail_target: 4,
9241            },
9242            // Match succeeded - set marker
9243            Instruction::LoadInt {
9244                value: 100,
9245                dest: Register(1),
9246            },
9247            Instruction::End,
9248            // Match failed
9249            Instruction::LoadInt {
9250                value: 0,
9251                dest: Register(1),
9252            },
9253            Instruction::End,
9254        ];
9255
9256        scheduler.spawn(program);
9257        run_to_idle(&mut scheduler);
9258
9259        let process = scheduler.processes.get(&Pid(0)).unwrap();
9260        assert_eq!(process.registers[1], Value::Int(100)); // Match succeeded
9261    }
9262
9263    #[test]
9264    fn test_binary_pattern_mismatch() {
9265        let mut scheduler = Scheduler::new();
9266
9267        let program = vec![
9268            // Create binary <<1, 2, 3>>
9269            Instruction::MakeBinary {
9270                bytes: vec![1, 2, 3],
9271                dest: Register(0),
9272            },
9273            // Try to match it against <<4, 5, 6>> (should fail)
9274            Instruction::Match {
9275                source: Register(0),
9276                pattern: Pattern::Binary(vec![4, 5, 6]),
9277                fail_target: 4,
9278            },
9279            // Match succeeded (shouldn't happen)
9280            Instruction::LoadInt {
9281                value: 100,
9282                dest: Register(1),
9283            },
9284            Instruction::End,
9285            // Match failed - set marker
9286            Instruction::LoadInt {
9287                value: 0,
9288                dest: Register(1),
9289            },
9290            Instruction::End,
9291        ];
9292
9293        scheduler.spawn(program);
9294        run_to_idle(&mut scheduler);
9295
9296        let process = scheduler.processes.get(&Pid(0)).unwrap();
9297        assert_eq!(process.registers[1], Value::Int(0)); // Match failed
9298    }
9299
9300    // ========== IO Tests ==========
9301
9302    #[test]
9303    fn test_println() {
9304        let mut scheduler = Scheduler::new();
9305
9306        let program = vec![
9307            Instruction::LoadInt {
9308                value: 42,
9309                dest: Register(0),
9310            },
9311            Instruction::PrintLn {
9312                source: Register(0),
9313            },
9314            Instruction::End,
9315        ];
9316
9317        scheduler.spawn(program);
9318        run_to_idle(&mut scheduler);
9319
9320        // Check output was captured
9321        assert_eq!(scheduler.output.len(), 1);
9322        assert!(scheduler.output[0].contains("42"));
9323    }
9324
9325    #[test]
9326    fn test_readline_eof_in_tests() {
9327        let mut scheduler = Scheduler::new();
9328
9329        let program = vec![
9330            Instruction::ReadLine { dest: Register(0) },
9331            Instruction::End,
9332        ];
9333
9334        scheduler.spawn(program);
9335        run_to_idle(&mut scheduler);
9336
9337        // In tests, ReadLine returns :eof
9338        let process = scheduler.processes.get(&Pid(0)).unwrap();
9339        assert_eq!(process.registers[0], Value::Atom("eof".to_string()));
9340    }
9341
9342    #[test]
9343    fn test_file_exists() {
9344        let mut scheduler = Scheduler::new();
9345
9346        // Use Cargo.toml which we know exists
9347        let program = vec![
9348            Instruction::MakeBinary {
9349                bytes: "Cargo.toml".as_bytes().to_vec(),
9350                dest: Register(0),
9351            },
9352            Instruction::BinaryToString {
9353                source: Register(0),
9354                dest: Register(0),
9355            },
9356            Instruction::FileExists {
9357                path: Register(0),
9358                dest: Register(1),
9359            },
9360            Instruction::End,
9361        ];
9362
9363        scheduler.spawn(program);
9364        run_to_idle(&mut scheduler);
9365
9366        let process = scheduler.processes.get(&Pid(0)).unwrap();
9367        assert_eq!(process.registers[1], Value::Int(1)); // Cargo.toml exists
9368    }
9369
9370    // ========== System Info Tests ==========
9371
9372    #[test]
9373    fn test_self_pid() {
9374        let mut scheduler = Scheduler::new();
9375
9376        let program = vec![Instruction::SelfPid { dest: Register(0) }, Instruction::End];
9377
9378        scheduler.spawn(program);
9379        run_to_idle(&mut scheduler);
9380
9381        let process = scheduler.processes.get(&Pid(0)).unwrap();
9382        assert_eq!(process.registers[0], Value::Pid(Pid(0)));
9383    }
9384
9385    #[test]
9386    fn test_process_count() {
9387        let mut scheduler = Scheduler::new();
9388
9389        // Spawn 3 processes
9390        let child_code = vec![Instruction::Work { amount: 100 }, Instruction::End];
9391
9392        let program = vec![
9393            Instruction::Spawn {
9394                code: child_code.clone(),
9395                dest: Register(0),
9396            },
9397            Instruction::Spawn {
9398                code: child_code.clone(),
9399                dest: Register(1),
9400            },
9401            Instruction::ProcessCount { dest: Register(2) },
9402            Instruction::End,
9403        ];
9404
9405        scheduler.spawn(program);
9406        // Just execute a few steps, don't run to completion
9407        scheduler.step(10);
9408
9409        let process = scheduler.processes.get(&Pid(0)).unwrap();
9410        // Should have 3 processes (main + 2 spawned)
9411        assert_eq!(process.registers[2], Value::Int(3));
9412    }
9413
9414    #[test]
9415    fn test_process_list() {
9416        let mut scheduler = Scheduler::new();
9417
9418        let child_code = vec![Instruction::Work { amount: 100 }, Instruction::End];
9419
9420        let program = vec![
9421            Instruction::Spawn {
9422                code: child_code,
9423                dest: Register(0),
9424            },
9425            Instruction::ProcessList { dest: Register(1) },
9426            Instruction::End,
9427        ];
9428
9429        scheduler.spawn(program);
9430        scheduler.step(10);
9431
9432        let process = scheduler.processes.get(&Pid(0)).unwrap();
9433        if let Value::List(pids) = &process.registers[1] {
9434            assert_eq!(pids.len(), 2); // main + 1 child
9435        } else {
9436            panic!("expected list");
9437        }
9438    }
9439
9440    #[test]
9441    fn test_is_alive() {
9442        let mut scheduler = Scheduler::new();
9443
9444        let program = vec![
9445            Instruction::SelfPid { dest: Register(0) },
9446            Instruction::IsAlive {
9447                pid: Register(0),
9448                dest: Register(1),
9449            },
9450            // Check a non-existent PID
9451            Instruction::LoadInt {
9452                value: 9999,
9453                dest: Register(2),
9454            },
9455            // We need to construct a Pid value - use spawn to get a real one then check after it ends
9456            Instruction::End,
9457        ];
9458
9459        scheduler.spawn(program);
9460        run_to_idle(&mut scheduler);
9461
9462        let process = scheduler.processes.get(&Pid(0)).unwrap();
9463        assert_eq!(process.registers[1], Value::Int(1)); // self is alive
9464    }
9465
9466    #[test]
9467    fn test_process_info() {
9468        let mut scheduler = Scheduler::new();
9469
9470        let program = vec![
9471            Instruction::SelfPid { dest: Register(0) },
9472            Instruction::ProcessInfo {
9473                pid: Register(0),
9474                dest: Register(1),
9475            },
9476            Instruction::End,
9477        ];
9478
9479        scheduler.spawn(program);
9480        run_to_idle(&mut scheduler);
9481
9482        let process = scheduler.processes.get(&Pid(0)).unwrap();
9483        if let Value::Tuple(info) = &process.registers[1] {
9484            assert_eq!(info.len(), 5);
9485            // Status should be "done" or "ready" depending on timing
9486            assert!(matches!(&info[0], Value::Atom(_)));
9487        } else {
9488            panic!("expected tuple");
9489        }
9490    }
9491
9492    #[test]
9493    fn test_module_list() {
9494        let mut scheduler = Scheduler::new();
9495
9496        // Load a module first
9497        let module = crate::Module {
9498            name: "test_mod".to_string(),
9499            code: vec![Instruction::End],
9500            functions: std::collections::HashMap::new(),
9501            exports: std::collections::HashSet::new(),
9502        };
9503        scheduler.load_module(module).unwrap();
9504
9505        let program = vec![
9506            Instruction::ModuleList { dest: Register(0) },
9507            Instruction::End,
9508        ];
9509
9510        scheduler.spawn(program);
9511        run_to_idle(&mut scheduler);
9512
9513        let process = scheduler.processes.get(&Pid(0)).unwrap();
9514        if let Value::List(modules) = &process.registers[0] {
9515            assert!(modules.contains(&Value::Atom("test_mod".to_string())));
9516        } else {
9517            panic!("expected list");
9518        }
9519    }
9520
9521    #[test]
9522    fn test_function_exported() {
9523        let mut scheduler = Scheduler::new();
9524
9525        // Load a module with an exported function
9526        let mut functions = std::collections::HashMap::new();
9527        functions.insert(
9528            ("my_func".to_string(), 0),
9529            crate::FunctionDef {
9530                name: "my_func".to_string(),
9531                arity: 0,
9532                entry: 0,
9533            },
9534        );
9535        let mut exports = std::collections::HashSet::new();
9536        exports.insert(("my_func".to_string(), 0));
9537
9538        let module = crate::Module {
9539            name: "test_mod".to_string(),
9540            code: vec![Instruction::End],
9541            functions,
9542            exports,
9543        };
9544        scheduler.load_module(module).unwrap();
9545
9546        let program = vec![
9547            Instruction::LoadAtom {
9548                name: "test_mod".to_string(),
9549                dest: Register(0),
9550            },
9551            Instruction::LoadAtom {
9552                name: "my_func".to_string(),
9553                dest: Register(1),
9554            },
9555            Instruction::LoadInt {
9556                value: 0,
9557                dest: Register(2),
9558            },
9559            Instruction::FunctionExported {
9560                module: Register(0),
9561                function: Register(1),
9562                arity: Register(2),
9563                dest: Register(3),
9564            },
9565            // Check non-existent function
9566            Instruction::LoadAtom {
9567                name: "no_func".to_string(),
9568                dest: Register(4),
9569            },
9570            Instruction::FunctionExported {
9571                module: Register(0),
9572                function: Register(4),
9573                arity: Register(2),
9574                dest: Register(5),
9575            },
9576            Instruction::End,
9577        ];
9578
9579        scheduler.spawn(program);
9580        run_to_idle(&mut scheduler);
9581
9582        let process = scheduler.processes.get(&Pid(0)).unwrap();
9583        assert_eq!(process.registers[3], Value::Int(1)); // my_func/0 exists
9584        assert_eq!(process.registers[5], Value::Int(0)); // no_func/0 doesn't exist
9585    }
9586
9587    // ========== BigInt Tests ==========
9588
9589    #[test]
9590    fn test_add_overflow_promotes_to_bigint() {
9591        let mut scheduler = Scheduler::new();
9592
9593        // i64::MAX + 1 should overflow and promote to BigInt
9594        let program = vec![
9595            Instruction::LoadInt {
9596                value: i64::MAX,
9597                dest: Register(0),
9598            },
9599            Instruction::Add {
9600                a: Operand::Reg(Register(0)),
9601                b: Operand::Int(1),
9602                dest: Register(1),
9603            },
9604            Instruction::End,
9605        ];
9606
9607        scheduler.spawn(program);
9608        run_to_idle(&mut scheduler);
9609
9610        let process = scheduler.processes.get(&Pid(0)).unwrap();
9611        // Result should be a BigInt
9612        let expected = BigInt::from(i64::MAX) + BigInt::from(1);
9613        assert_eq!(process.registers[1], Value::BigInt(expected));
9614    }
9615
9616    #[test]
9617    fn test_sub_overflow_promotes_to_bigint() {
9618        let mut scheduler = Scheduler::new();
9619
9620        // i64::MIN - 1 should underflow and promote to BigInt
9621        let program = vec![
9622            Instruction::LoadInt {
9623                value: i64::MIN,
9624                dest: Register(0),
9625            },
9626            Instruction::Sub {
9627                a: Operand::Reg(Register(0)),
9628                b: Operand::Int(1),
9629                dest: Register(1),
9630            },
9631            Instruction::End,
9632        ];
9633
9634        scheduler.spawn(program);
9635        run_to_idle(&mut scheduler);
9636
9637        let process = scheduler.processes.get(&Pid(0)).unwrap();
9638        // Result should be a BigInt
9639        let expected = BigInt::from(i64::MIN) - BigInt::from(1);
9640        assert_eq!(process.registers[1], Value::BigInt(expected));
9641    }
9642
9643    #[test]
9644    fn test_mul_overflow_promotes_to_bigint() {
9645        let mut scheduler = Scheduler::new();
9646
9647        // Large multiplication that overflows
9648        let program = vec![
9649            Instruction::LoadInt {
9650                value: i64::MAX / 2 + 1,
9651                dest: Register(0),
9652            },
9653            Instruction::Mul {
9654                a: Operand::Reg(Register(0)),
9655                b: Operand::Int(3),
9656                dest: Register(1),
9657            },
9658            Instruction::End,
9659        ];
9660
9661        scheduler.spawn(program);
9662        run_to_idle(&mut scheduler);
9663
9664        let process = scheduler.processes.get(&Pid(0)).unwrap();
9665        // Result should be a BigInt
9666        let val = i64::MAX / 2 + 1;
9667        let expected = BigInt::from(val) * BigInt::from(3);
9668        assert_eq!(process.registers[1], Value::BigInt(expected));
9669    }
9670
9671    #[test]
9672    fn test_bigint_to_int_normalization() {
9673        let mut scheduler = Scheduler::new();
9674
9675        // i64::MAX + 1 - 1 should normalize back to Int
9676        let program = vec![
9677            Instruction::LoadInt {
9678                value: i64::MAX,
9679                dest: Register(0),
9680            },
9681            Instruction::Add {
9682                a: Operand::Reg(Register(0)),
9683                b: Operand::Int(1),
9684                dest: Register(1),
9685            },
9686            // Now subtract 1 to get back to i64 range
9687            Instruction::Sub {
9688                a: Operand::Reg(Register(1)),
9689                b: Operand::Int(1),
9690                dest: Register(2),
9691            },
9692            Instruction::End,
9693        ];
9694
9695        scheduler.spawn(program);
9696        run_to_idle(&mut scheduler);
9697
9698        let process = scheduler.processes.get(&Pid(0)).unwrap();
9699        // After subtracting 1, should normalize back to Int
9700        assert_eq!(process.registers[2], Value::Int(i64::MAX));
9701    }
9702
9703    #[test]
9704    fn test_bigint_comparison() {
9705        let mut scheduler = Scheduler::new();
9706
9707        // Compare BigInt values
9708        let program = vec![
9709            // Create two BigInts: i64::MAX + 1 and i64::MAX + 2
9710            Instruction::LoadInt {
9711                value: i64::MAX,
9712                dest: Register(0),
9713            },
9714            Instruction::Add {
9715                a: Operand::Reg(Register(0)),
9716                b: Operand::Int(1),
9717                dest: Register(1), // i64::MAX + 1
9718            },
9719            Instruction::Add {
9720                a: Operand::Reg(Register(0)),
9721                b: Operand::Int(2),
9722                dest: Register(2), // i64::MAX + 2
9723            },
9724            // Compare: (i64::MAX + 1) < (i64::MAX + 2)
9725            Instruction::Lt {
9726                a: Operand::Reg(Register(1)),
9727                b: Operand::Reg(Register(2)),
9728                dest: Register(3),
9729            },
9730            // Compare: (i64::MAX + 2) > (i64::MAX + 1)
9731            Instruction::Gt {
9732                a: Operand::Reg(Register(2)),
9733                b: Operand::Reg(Register(1)),
9734                dest: Register(4),
9735            },
9736            // Compare: (i64::MAX + 1) == (i64::MAX + 1)
9737            Instruction::Eq {
9738                a: Operand::Reg(Register(1)),
9739                b: Operand::Reg(Register(1)),
9740                dest: Register(5),
9741            },
9742            Instruction::End,
9743        ];
9744
9745        scheduler.spawn(program);
9746        run_to_idle(&mut scheduler);
9747
9748        let process = scheduler.processes.get(&Pid(0)).unwrap();
9749        assert_eq!(process.registers[3], Value::Int(1)); // true: first < second
9750        assert_eq!(process.registers[4], Value::Int(1)); // true: second > first
9751        assert_eq!(process.registers[5], Value::Int(1)); // true: equal to itself
9752    }
9753
9754    #[test]
9755    fn test_bigint_div() {
9756        let mut scheduler = Scheduler::new();
9757
9758        // Divide a BigInt by an Int
9759        let program = vec![
9760            Instruction::LoadInt {
9761                value: i64::MAX,
9762                dest: Register(0),
9763            },
9764            Instruction::Add {
9765                a: Operand::Reg(Register(0)),
9766                b: Operand::Reg(Register(0)),
9767                dest: Register(1), // 2 * i64::MAX (BigInt)
9768            },
9769            Instruction::Div {
9770                a: Operand::Reg(Register(1)),
9771                b: Operand::Int(2),
9772                dest: Register(2),
9773            },
9774            Instruction::End,
9775        ];
9776
9777        scheduler.spawn(program);
9778        run_to_idle(&mut scheduler);
9779
9780        let process = scheduler.processes.get(&Pid(0)).unwrap();
9781        // 2 * i64::MAX / 2 = i64::MAX, should normalize back to Int
9782        assert_eq!(process.registers[2], Value::Int(i64::MAX));
9783    }
9784
9785    #[test]
9786    fn test_bigint_mod() {
9787        let mut scheduler = Scheduler::new();
9788
9789        // Modulo with BigInt
9790        let program = vec![
9791            Instruction::LoadInt {
9792                value: i64::MAX,
9793                dest: Register(0),
9794            },
9795            Instruction::Add {
9796                a: Operand::Reg(Register(0)),
9797                b: Operand::Int(5),
9798                dest: Register(1), // i64::MAX + 5 (BigInt)
9799            },
9800            Instruction::Mod {
9801                a: Operand::Reg(Register(1)),
9802                b: Operand::Int(10),
9803                dest: Register(2),
9804            },
9805            Instruction::End,
9806        ];
9807
9808        scheduler.spawn(program);
9809        run_to_idle(&mut scheduler);
9810
9811        let process = scheduler.processes.get(&Pid(0)).unwrap();
9812        // (i64::MAX + 5) % 10 = (7 + 5) % 10 = 2 (since i64::MAX % 10 = 7)
9813        let expected = (BigInt::from(i64::MAX) + BigInt::from(5)) % BigInt::from(10);
9814        assert_eq!(process.registers[2], Value::from_bigint(expected));
9815    }
9816
9817    #[test]
9818    fn test_int_bigint_cross_comparison() {
9819        let mut scheduler = Scheduler::new();
9820
9821        // Compare Int with BigInt
9822        let program = vec![
9823            Instruction::LoadInt {
9824                value: i64::MAX,
9825                dest: Register(0),
9826            },
9827            Instruction::Add {
9828                a: Operand::Reg(Register(0)),
9829                b: Operand::Int(1),
9830                dest: Register(1), // BigInt: i64::MAX + 1
9831            },
9832            // Compare Int < BigInt
9833            Instruction::Lt {
9834                a: Operand::Reg(Register(0)),
9835                b: Operand::Reg(Register(1)),
9836                dest: Register(2),
9837            },
9838            // Compare BigInt > Int
9839            Instruction::Gt {
9840                a: Operand::Reg(Register(1)),
9841                b: Operand::Reg(Register(0)),
9842                dest: Register(3),
9843            },
9844            Instruction::End,
9845        ];
9846
9847        scheduler.spawn(program);
9848        run_to_idle(&mut scheduler);
9849
9850        let process = scheduler.processes.get(&Pid(0)).unwrap();
9851        assert_eq!(process.registers[2], Value::Int(1)); // i64::MAX < i64::MAX + 1
9852        assert_eq!(process.registers[3], Value::Int(1)); // i64::MAX + 1 > i64::MAX
9853    }
9854
9855    // ========== Bit Syntax Tests ==========
9856
9857    #[test]
9858    fn test_binary_construct_segments() {
9859        let mut scheduler = Scheduler::new();
9860
9861        // Construct a binary with two 8-bit integers
9862        let program = vec![
9863            Instruction::BinaryConstructSegments {
9864                segments: vec![
9865                    (
9866                        crate::SegmentSource::Int(0x12),
9867                        crate::BitSegment::integer(8),
9868                    ),
9869                    (
9870                        crate::SegmentSource::Int(0x34),
9871                        crate::BitSegment::integer(8),
9872                    ),
9873                ],
9874                dest: Register(0),
9875            },
9876            Instruction::End,
9877        ];
9878
9879        scheduler.spawn(program);
9880        run_to_idle(&mut scheduler);
9881
9882        let process = scheduler.processes.get(&Pid(0)).unwrap();
9883        assert_eq!(process.registers[0], Value::Binary(vec![0x12, 0x34]));
9884    }
9885
9886    #[test]
9887    fn test_binary_construct_16bit_big_endian() {
9888        let mut scheduler = Scheduler::new();
9889
9890        // Construct a binary with a 16-bit big-endian integer
9891        let program = vec![
9892            Instruction::BinaryConstructSegments {
9893                segments: vec![(
9894                    crate::SegmentSource::Int(0x1234),
9895                    crate::BitSegment::integer(16),
9896                )],
9897                dest: Register(0),
9898            },
9899            Instruction::End,
9900        ];
9901
9902        scheduler.spawn(program);
9903        run_to_idle(&mut scheduler);
9904
9905        let process = scheduler.processes.get(&Pid(0)).unwrap();
9906        assert_eq!(process.registers[0], Value::Binary(vec![0x12, 0x34]));
9907    }
9908
9909    #[test]
9910    fn test_binary_construct_16bit_little_endian() {
9911        let mut scheduler = Scheduler::new();
9912
9913        // Construct a binary with a 16-bit little-endian integer
9914        let program = vec![
9915            Instruction::BinaryConstructSegments {
9916                segments: vec![(
9917                    crate::SegmentSource::Int(0x1234),
9918                    crate::BitSegment::integer(16).little(),
9919                )],
9920                dest: Register(0),
9921            },
9922            Instruction::End,
9923        ];
9924
9925        scheduler.spawn(program);
9926        run_to_idle(&mut scheduler);
9927
9928        let process = scheduler.processes.get(&Pid(0)).unwrap();
9929        assert_eq!(process.registers[0], Value::Binary(vec![0x34, 0x12]));
9930    }
9931
9932    #[test]
9933    fn test_binary_construct_with_register() {
9934        let mut scheduler = Scheduler::new();
9935
9936        let program = vec![
9937            Instruction::LoadInt {
9938                value: 0xAB,
9939                dest: Register(0),
9940            },
9941            Instruction::BinaryConstructSegments {
9942                segments: vec![
9943                    (
9944                        crate::SegmentSource::Reg(Register(0)),
9945                        crate::BitSegment::integer(8),
9946                    ),
9947                    (
9948                        crate::SegmentSource::Int(0xCD),
9949                        crate::BitSegment::integer(8),
9950                    ),
9951                ],
9952                dest: Register(1),
9953            },
9954            Instruction::End,
9955        ];
9956
9957        scheduler.spawn(program);
9958        run_to_idle(&mut scheduler);
9959
9960        let process = scheduler.processes.get(&Pid(0)).unwrap();
9961        assert_eq!(process.registers[1], Value::Binary(vec![0xAB, 0xCD]));
9962    }
9963
9964    #[test]
9965    fn test_binary_match_segments() {
9966        let mut scheduler = Scheduler::new();
9967
9968        let program = vec![
9969            // Create a binary <<0x12, 0x34, 0x56, 0x78>>
9970            Instruction::MakeBinary {
9971                bytes: vec![0x12, 0x34, 0x56, 0x78],
9972                dest: Register(0),
9973            },
9974            // Start matching
9975            Instruction::BinaryMatchStart {
9976                source: Register(0),
9977            },
9978            // Match first 8-bit segment
9979            Instruction::BinaryMatchSegment {
9980                segment: crate::BitSegment::integer(8),
9981                dest: Register(1),
9982                fail_target: 100,
9983            },
9984            // Match second 16-bit segment (big endian)
9985            Instruction::BinaryMatchSegment {
9986                segment: crate::BitSegment::integer(16),
9987                dest: Register(2),
9988                fail_target: 100,
9989            },
9990            // Get the rest
9991            Instruction::BinaryMatchRest { dest: Register(3) },
9992            Instruction::End,
9993        ];
9994
9995        scheduler.spawn(program);
9996        run_to_idle(&mut scheduler);
9997
9998        let process = scheduler.processes.get(&Pid(0)).unwrap();
9999        assert_eq!(process.registers[1], Value::Int(0x12));
10000        assert_eq!(process.registers[2], Value::Int(0x3456));
10001        assert_eq!(process.registers[3], Value::Binary(vec![0x78]));
10002    }
10003
10004    #[test]
10005    fn test_binary_match_little_endian() {
10006        let mut scheduler = Scheduler::new();
10007
10008        let program = vec![
10009            Instruction::MakeBinary {
10010                bytes: vec![0x34, 0x12],
10011                dest: Register(0),
10012            },
10013            Instruction::BinaryMatchStart {
10014                source: Register(0),
10015            },
10016            Instruction::BinaryMatchSegment {
10017                segment: crate::BitSegment::integer(16).little(),
10018                dest: Register(1),
10019                fail_target: 100,
10020            },
10021            Instruction::End,
10022        ];
10023
10024        scheduler.spawn(program);
10025        run_to_idle(&mut scheduler);
10026
10027        let process = scheduler.processes.get(&Pid(0)).unwrap();
10028        assert_eq!(process.registers[1], Value::Int(0x1234));
10029    }
10030
10031    #[test]
10032    fn test_binary_match_signed() {
10033        let mut scheduler = Scheduler::new();
10034
10035        let program = vec![
10036            // 0xFF as signed 8-bit is -1
10037            Instruction::MakeBinary {
10038                bytes: vec![0xFF],
10039                dest: Register(0),
10040            },
10041            Instruction::BinaryMatchStart {
10042                source: Register(0),
10043            },
10044            Instruction::BinaryMatchSegment {
10045                segment: crate::BitSegment::integer(8).signed(),
10046                dest: Register(1),
10047                fail_target: 100,
10048            },
10049            Instruction::End,
10050        ];
10051
10052        scheduler.spawn(program);
10053        run_to_idle(&mut scheduler);
10054
10055        let process = scheduler.processes.get(&Pid(0)).unwrap();
10056        assert_eq!(process.registers[1], Value::Int(-1));
10057    }
10058
10059    #[test]
10060    fn test_binary_match_float64() {
10061        let mut scheduler = Scheduler::new();
10062
10063        // Create binary from f64 bytes (big endian)
10064        let f: f64 = 3.14159;
10065        let bytes = f.to_be_bytes().to_vec();
10066
10067        let program = vec![
10068            Instruction::MakeBinary {
10069                bytes: bytes.clone(),
10070                dest: Register(0),
10071            },
10072            Instruction::BinaryMatchStart {
10073                source: Register(0),
10074            },
10075            Instruction::BinaryMatchSegment {
10076                segment: crate::BitSegment::float64(),
10077                dest: Register(1),
10078                fail_target: 100,
10079            },
10080            Instruction::End,
10081        ];
10082
10083        scheduler.spawn(program);
10084        run_to_idle(&mut scheduler);
10085
10086        let process = scheduler.processes.get(&Pid(0)).unwrap();
10087        assert_eq!(process.registers[1], Value::Float(3.14159));
10088    }
10089
10090    #[test]
10091    fn test_binary_match_fail_not_enough_bytes() {
10092        let mut scheduler = Scheduler::new();
10093
10094        let program = vec![
10095            Instruction::MakeBinary {
10096                bytes: vec![0x12],
10097                dest: Register(0),
10098            },
10099            Instruction::BinaryMatchStart {
10100                source: Register(0),
10101            },
10102            // Try to match 16 bits from a 1-byte binary - should fail
10103            Instruction::BinaryMatchSegment {
10104                segment: crate::BitSegment::integer(16),
10105                dest: Register(1),
10106                fail_target: 5, // Jump to LoadInt at index 5
10107            },
10108            Instruction::LoadInt {
10109                value: 999, // Should not reach here
10110                dest: Register(2),
10111            },
10112            Instruction::End,
10113            // Fail target: index 5
10114            Instruction::LoadInt {
10115                value: -1, // Match failed indicator
10116                dest: Register(2),
10117            },
10118            Instruction::End,
10119        ];
10120
10121        scheduler.spawn(program);
10122        run_to_idle(&mut scheduler);
10123
10124        let process = scheduler.processes.get(&Pid(0)).unwrap();
10125        assert_eq!(process.registers[2], Value::Int(-1));
10126    }
10127
10128    #[test]
10129    fn test_binary_get_integer() {
10130        let mut scheduler = Scheduler::new();
10131
10132        let program = vec![
10133            Instruction::MakeBinary {
10134                bytes: vec![0x00, 0x12, 0x34, 0x56],
10135                dest: Register(0),
10136            },
10137            Instruction::LoadInt {
10138                value: 8, // bit offset
10139                dest: Register(1),
10140            },
10141            Instruction::BinaryGetInteger {
10142                bin: Register(0),
10143                bit_offset: Register(1),
10144                segment: crate::BitSegment::integer(16),
10145                dest: Register(2),
10146            },
10147            Instruction::End,
10148        ];
10149
10150        scheduler.spawn(program);
10151        run_to_idle(&mut scheduler);
10152
10153        let process = scheduler.processes.get(&Pid(0)).unwrap();
10154        assert_eq!(process.registers[2], Value::Int(0x1234));
10155    }
10156
10157    #[test]
10158    fn test_binary_put_integer() {
10159        let mut scheduler = Scheduler::new();
10160
10161        let program = vec![
10162            Instruction::MakeBinary {
10163                bytes: vec![0x00, 0x00, 0x00, 0x00],
10164                dest: Register(0),
10165            },
10166            Instruction::LoadInt {
10167                value: 8, // bit offset
10168                dest: Register(1),
10169            },
10170            Instruction::LoadInt {
10171                value: 0xABCD, // value to insert
10172                dest: Register(2),
10173            },
10174            Instruction::BinaryPutInteger {
10175                bin: Register(0),
10176                bit_offset: Register(1),
10177                value: Register(2),
10178                segment: crate::BitSegment::integer(16),
10179                dest: Register(3),
10180            },
10181            Instruction::End,
10182        ];
10183
10184        scheduler.spawn(program);
10185        run_to_idle(&mut scheduler);
10186
10187        let process = scheduler.processes.get(&Pid(0)).unwrap();
10188        assert_eq!(
10189            process.registers[3],
10190            Value::Binary(vec![0x00, 0xAB, 0xCD, 0x00])
10191        );
10192    }
10193
10194    #[test]
10195    fn test_binary_construct_and_match_roundtrip() {
10196        let mut scheduler = Scheduler::new();
10197
10198        // Construct a binary, then match it back
10199        let program = vec![
10200            // Construct <<0x12:8, 0x3456:16/big, 0x78:8>>
10201            Instruction::BinaryConstructSegments {
10202                segments: vec![
10203                    (
10204                        crate::SegmentSource::Int(0x12),
10205                        crate::BitSegment::integer(8),
10206                    ),
10207                    (
10208                        crate::SegmentSource::Int(0x3456),
10209                        crate::BitSegment::integer(16),
10210                    ),
10211                    (
10212                        crate::SegmentSource::Int(0x78),
10213                        crate::BitSegment::integer(8),
10214                    ),
10215                ],
10216                dest: Register(0),
10217            },
10218            // Now match it back
10219            Instruction::BinaryMatchStart {
10220                source: Register(0),
10221            },
10222            Instruction::BinaryMatchSegment {
10223                segment: crate::BitSegment::integer(8),
10224                dest: Register(1),
10225                fail_target: 100,
10226            },
10227            Instruction::BinaryMatchSegment {
10228                segment: crate::BitSegment::integer(16),
10229                dest: Register(2),
10230                fail_target: 100,
10231            },
10232            Instruction::BinaryMatchSegment {
10233                segment: crate::BitSegment::integer(8),
10234                dest: Register(3),
10235                fail_target: 100,
10236            },
10237            Instruction::End,
10238        ];
10239
10240        scheduler.spawn(program);
10241        run_to_idle(&mut scheduler);
10242
10243        let process = scheduler.processes.get(&Pid(0)).unwrap();
10244        assert_eq!(
10245            process.registers[0],
10246            Value::Binary(vec![0x12, 0x34, 0x56, 0x78])
10247        );
10248        assert_eq!(process.registers[1], Value::Int(0x12));
10249        assert_eq!(process.registers[2], Value::Int(0x3456));
10250        assert_eq!(process.registers[3], Value::Int(0x78));
10251    }
10252}