use std::convert::TryFrom; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum OpCode { // 6.1 Controle de Execução Nop = 0x00, Halt = 0x01, Jmp = 0x02, JmpIfFalse = 0x03, // 6.2 Pilha PushConst = 0x10, Pop = 0x11, Dup = 0x12, Swap = 0x13, // 6.3 Aritmética Add = 0x20, Sub = 0x21, Mul = 0x22, Div = 0x23, // 6.4 Comparação e Lógica Eq = 0x30, Neq = 0x31, Lt = 0x32, Gt = 0x33, And = 0x34, Or = 0x35, Not = 0x36, // 6.5 Variáveis GetGlobal = 0x40, SetGlobal = 0x41, GetLocal = 0x42, SetLocal = 0x43, // 6.6 Funções Call = 0x50, Ret = 0x51, PushScope = 0x52, PopScope = 0x53, // 6.7 Heap Alloc = 0x60, LoadRef = 0x61, StoreRef = 0x62, // 6.8 Periféricos e Sistema Syscall = 0x70, FrameSync = 0x80, } impl TryFrom for OpCode { type Error = String; fn try_from(value: u16) -> Result { match value { 0x00 => Ok(OpCode::Nop), 0x01 => Ok(OpCode::Halt), 0x02 => Ok(OpCode::Jmp), 0x03 => Ok(OpCode::JmpIfFalse), 0x10 => Ok(OpCode::PushConst), 0x11 => Ok(OpCode::Pop), 0x12 => Ok(OpCode::Dup), 0x13 => Ok(OpCode::Swap), 0x20 => Ok(OpCode::Add), 0x21 => Ok(OpCode::Sub), 0x22 => Ok(OpCode::Mul), 0x23 => Ok(OpCode::Div), 0x30 => Ok(OpCode::Eq), 0x31 => Ok(OpCode::Neq), 0x32 => Ok(OpCode::Lt), 0x33 => Ok(OpCode::Gt), 0x34 => Ok(OpCode::And), 0x35 => Ok(OpCode::Or), 0x36 => Ok(OpCode::Not), 0x40 => Ok(OpCode::GetGlobal), 0x41 => Ok(OpCode::SetGlobal), 0x42 => Ok(OpCode::GetLocal), 0x43 => Ok(OpCode::SetLocal), 0x50 => Ok(OpCode::Call), 0x51 => Ok(OpCode::Ret), 0x52 => Ok(OpCode::PushScope), 0x53 => Ok(OpCode::PopScope), 0x60 => Ok(OpCode::Alloc), 0x61 => Ok(OpCode::LoadRef), 0x62 => Ok(OpCode::StoreRef), 0x70 => Ok(OpCode::Syscall), 0x80 => Ok(OpCode::FrameSync), _ => Err(format!("Invalid OpCode: 0x{:04X}", value)), } } } impl OpCode { pub fn cycles(&self) -> u64 { match self { OpCode::Nop => 1, OpCode::Halt => 1, OpCode::Jmp => 2, OpCode::JmpIfFalse => 3, OpCode::PushConst => 2, OpCode::Pop => 1, OpCode::Dup => 1, OpCode::Swap => 1, OpCode::Add => 2, OpCode::Sub => 2, OpCode::Mul => 4, OpCode::Div => 6, OpCode::Eq => 2, OpCode::Neq => 2, OpCode::Lt => 2, OpCode::Gt => 2, OpCode::And => 2, OpCode::Or => 2, OpCode::Not => 1, OpCode::GetGlobal => 3, OpCode::SetGlobal => 3, OpCode::GetLocal => 2, OpCode::SetLocal => 2, OpCode::Call => 5, OpCode::Ret => 4, OpCode::PushScope => 3, OpCode::PopScope => 3, OpCode::Alloc => 10, OpCode::LoadRef => 3, OpCode::StoreRef => 3, OpCode::Syscall => 1, // Variável, mas vamos usar 1 como base ou definir via ID OpCode::FrameSync => 1, } } } #[derive(Debug, Clone)] pub enum Value { Integer(i64), Float(f64), Boolean(bool), String(String), Ref(usize), // Referência ao heap Null, } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::Integer(a), Value::Integer(b)) => a == b, (Value::Float(a), Value::Float(b)) => a == b, (Value::Integer(a), Value::Float(b)) => *a as f64 == *b, (Value::Float(a), Value::Integer(b)) => *a == *b as f64, (Value::Boolean(a), Value::Boolean(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Ref(a), Value::Ref(b)) => a == b, (Value::Null, Value::Null) => true, _ => false, } } } impl Value { pub fn as_float(&self) -> Option { match self { Value::Integer(i) => Some(*i as f64), Value::Float(f) => Some(*f), _ => None, } } pub fn as_integer(&self) -> Option { match self { Value::Integer(i) => Some(*i), Value::Float(f) => Some(*f as i64), _ => None, } } } pub struct CallFrame { pub return_address: usize, pub stack_base: usize, pub locals_count: usize, } pub trait NativeInterface { fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result; } pub struct VirtualMachine { pub pc: usize, pub operand_stack: Vec, pub call_stack: Vec, pub globals: Vec, pub constant_pool: Vec, pub rom: Vec, pub heap: Vec, // Simplificado para demo, futuramente RAM/Heap real pub cycles: u64, pub halted: bool, } impl VirtualMachine { pub fn new(rom: Vec, constant_pool: Vec) -> Self { Self { pc: 0, operand_stack: Vec::new(), call_stack: Vec::new(), globals: Vec::new(), constant_pool, rom, heap: Vec::new(), cycles: 0, halted: false, } } /// Cria uma VM com um "Boot ROM" básico que exibe uma animação de padrão de teste. /// Isso garante que a máquina sempre mostre algo ao ser ligada, mesmo sem cartucho. pub fn new_boot_rom() -> Self { let mut rom = Vec::new(); let mut constant_pool = Vec::new(); // Constantes do Boot ROM constant_pool.push(Value::Integer(140)); // 0: x constant_pool.push(Value::Integer(70)); // 1: y constant_pool.push(Value::Integer(40)); // 2: largura constant_pool.push(Value::Integer(40)); // 3: altura constant_pool.push(Value::Integer(7)); // 4: cor rect (indigo) constant_pool.push(Value::Integer(0)); // 5: sample_id (square) constant_pool.push(Value::Integer(0)); // 6: voice_id constant_pool.push(Value::Integer(255)); // 7: volume constant_pool.push(Value::Integer(127)); // 8: pan (center) constant_pool.push(Value::Float(1.0)); // 9: pitch constant_pool.push(Value::Integer(0)); // 10: bg color (black) constant_pool.push(Value::Integer(10)); // 11: button START // -- PROGRAMA -- // 1. Toca o som de boot "plim" (uma vez) // Push arguments for audio.play_sample(sample_id, voice_id, volume, pan, pitch) rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&5u32.to_le_bytes()); // sample_id rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&6u32.to_le_bytes()); // voice_id rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&7u32.to_le_bytes()); // volume rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&8u32.to_le_bytes()); // pan rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&9u32.to_le_bytes()); // pitch rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x3001u32.to_le_bytes()); let loop_start = rom.len() as u32; // 2. Verifica Cartucho e Input para Boot // system.has_cart? rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x0001u32.to_le_bytes()); let jmp_no_cart_idx = rom.len(); rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder // Se tem cartucho, checa START rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&11u32.to_le_bytes()); // START button ID rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x2001u32.to_le_bytes()); // input.get_pad let jmp_no_start_idx = rom.len(); rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder // Se tem cartucho E START, run_cart! rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x0002u32.to_le_bytes()); // system.run_cart // Destino para quando não tem cartucho ou não apertou START let skip_cart_addr = rom.len() as u32; // Patch placeholders let skip_bytes = skip_cart_addr.to_le_bytes(); rom[jmp_no_cart_idx+2..jmp_no_cart_idx+6].copy_from_slice(&skip_bytes); rom[jmp_no_start_idx+2..jmp_no_start_idx+6].copy_from_slice(&skip_bytes); // 3. Limpa a tela rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&10u32.to_le_bytes()); // bg color rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x1001u32.to_le_bytes()); // 4. Desenha o quadrado no centro rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // x rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // y rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&2u32.to_le_bytes()); // w rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&3u32.to_le_bytes()); // h rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&4u32.to_le_bytes()); // color rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x1002u32.to_le_bytes()); // 4. Sincroniza frame rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes()); // 5. Loop infinito rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); rom.extend_from_slice(&loop_start.to_le_bytes()); Self::new(rom, constant_pool) } } impl Default for VirtualMachine { fn default() -> Self { Self::new_boot_rom() } } impl VirtualMachine { pub fn run_budget(&mut self, budget: u64, native: &mut dyn NativeInterface) -> Result { let start_cycles = self.cycles; let mut budget_used = 0; while budget_used < budget && !self.halted && self.pc < self.rom.len() { let pc_before = self.pc; let cycles_before = self.cycles; let opcode_val = self.peek_u16()?; let opcode = OpCode::try_from(opcode_val)?; if opcode == OpCode::FrameSync { self.pc += 2; self.cycles += OpCode::FrameSync.cycles(); break; } self.step(native)?; // Garantir progresso para evitar loop infinito real (onde nem PC nem ciclos avançam) if self.pc == pc_before && self.cycles == cycles_before && !self.halted { return Err(format!("VM stuck at PC 0x{:08X}", self.pc)); } budget_used = self.cycles - start_cycles; } Ok(budget_used) } fn peek_u16(&self) -> Result { if self.pc + 2 > self.rom.len() { return Err("Unexpected end of ROM".into()); } let bytes = [ self.rom[self.pc], self.rom[self.pc + 1], ]; Ok(u16::from_le_bytes(bytes)) } pub fn step(&mut self, native: &mut dyn NativeInterface) -> Result<(), String> { if self.halted || self.pc >= self.rom.len() { return Ok(()); } let opcode_val = self.read_u16()?; let opcode = OpCode::try_from(opcode_val)?; match opcode { OpCode::Nop => {} OpCode::Halt => { self.halted = true; } OpCode::Jmp => { let addr = self.read_u32()? as usize; self.pc = addr; } OpCode::JmpIfFalse => { let addr = self.read_u32()? as usize; let val = self.pop()?; if let Value::Boolean(false) = val { self.pc = addr; } } OpCode::PushConst => { let idx = self.read_u32()? as usize; let val = self.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?; self.push(val); } OpCode::Pop => { self.pop()?; } OpCode::Dup => { let val = self.peek()?.clone(); self.push(val); } OpCode::Swap => { let a = self.pop()?; let b = self.pop()?; self.push(a); self.push(b); } OpCode::Add => self.binary_op(|a, b| match (a, b) { (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_add(b))), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)), (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a + b as f64)), _ => Err("Invalid types for ADD".into()), })?, OpCode::Sub => self.binary_op(|a, b| match (a, b) { (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_sub(b))), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)), (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)), (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a - b as f64)), _ => Err("Invalid types for SUB".into()), })?, OpCode::Mul => self.binary_op(|a, b| match (a, b) { (Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_mul(b))), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)), (Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)), (Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a * b as f64)), _ => Err("Invalid types for MUL".into()), })?, OpCode::Div => self.binary_op(|a, b| match (a, b) { (Value::Integer(a), Value::Integer(b)) => { if b == 0 { return Err("Division by zero".into()); } Ok(Value::Integer(a / b)) } (Value::Float(a), Value::Float(b)) => { if b == 0.0 { return Err("Division by zero".into()); } Ok(Value::Float(a / b)) } (Value::Integer(a), Value::Float(b)) => { if b == 0.0 { return Err("Division by zero".into()); } Ok(Value::Float(a as f64 / b)) } (Value::Float(a), Value::Integer(b)) => { if b == 0 { return Err("Division by zero".into()); } Ok(Value::Float(a / b as f64)) } _ => Err("Invalid types for DIV".into()), })?, OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b)))?, OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b)))?, OpCode::Lt => self.binary_op(|a, b| { match (a, b) { (Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a < b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a < b)), (Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) < b)), (Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a < (b as f64))), _ => Err("Invalid types for LT".into()), } })?, OpCode::Gt => self.binary_op(|a, b| { match (a, b) { (Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a > b)), (Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a > b)), (Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) > b)), (Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a > (b as f64))), _ => Err("Invalid types for GT".into()), } })?, OpCode::And => self.binary_op(|a, b| match (a, b) { (Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)), _ => Err("Invalid types for AND".into()), })?, OpCode::Or => self.binary_op(|a, b| match (a, b) { (Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a || b)), _ => Err("Invalid types for OR".into()), })?, OpCode::Not => { let val = self.pop()?; if let Value::Boolean(b) = val { self.push(Value::Boolean(!b)); } else { return Err("Invalid type for NOT".into()); } } OpCode::GetGlobal => { let idx = self.read_u32()? as usize; let val = self.globals.get(idx).cloned().ok_or("Invalid global index")?; self.push(val); } OpCode::SetGlobal => { let idx = self.read_u32()? as usize; let val = self.pop()?; if idx >= self.globals.len() { self.globals.resize(idx + 1, Value::Null); } self.globals[idx] = val; } OpCode::GetLocal => { let idx = self.read_u32()? as usize; let frame = self.call_stack.last().ok_or("No active call frame")?; let val = self.operand_stack.get(frame.stack_base + idx).cloned().ok_or("Invalid local index")?; self.push(val); } OpCode::SetLocal => { let idx = self.read_u32()? as usize; let val = self.pop()?; let frame = self.call_stack.last().ok_or("No active call frame")?; let stack_idx = frame.stack_base + idx; if stack_idx >= self.operand_stack.len() { return Err("Local index out of bounds".into()); } self.operand_stack[stack_idx] = val; } OpCode::Call => { let addr = self.read_u32()? as usize; let args_count = self.read_u32()? as usize; let stack_base = self.operand_stack.len() - args_count; self.call_stack.push(CallFrame { return_address: self.pc, stack_base, locals_count: args_count, }); self.pc = addr; } OpCode::Ret => { let frame = self.call_stack.pop().ok_or("Call stack underflow")?; let return_val = self.pop()?; self.operand_stack.truncate(frame.stack_base); self.push(return_val); self.pc = frame.return_address; } OpCode::PushScope => { let locals_count = self.read_u32()? as usize; let stack_base = self.operand_stack.len(); for _ in 0..locals_count { self.push(Value::Null); } self.call_stack.push(CallFrame { return_address: 0, stack_base, locals_count, }); } OpCode::PopScope => { let frame = self.call_stack.pop().ok_or("Call stack underflow")?; self.operand_stack.truncate(frame.stack_base); } OpCode::Alloc => { let size = self.read_u32()? as usize; let ref_idx = self.heap.len(); for _ in 0..size { self.heap.push(Value::Null); } self.push(Value::Ref(ref_idx)); } OpCode::LoadRef => { let offset = self.read_u32()? as usize; let ref_val = self.pop()?; if let Value::Ref(base) = ref_val { let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?; self.push(val); } else { return Err("Expected reference for LOAD_REF".into()); } } OpCode::StoreRef => { let offset = self.read_u32()? as usize; let val = self.pop()?; let ref_val = self.pop()?; if let Value::Ref(base) = ref_val { if base + offset >= self.heap.len() { return Err("Invalid heap access".into()); } self.heap[base + offset] = val; } else { return Err("Expected reference for STORE_REF".into()); } } OpCode::Syscall => { let id = self.read_u32()?; let native_cycles = native.call(id, self).map_err(|e| format!("Native call 0x{:08X} failed: {}", id, e))?; self.cycles += native_cycles; } OpCode::FrameSync => { return Ok(()); } } self.cycles += opcode.cycles(); Ok(()) } fn read_u32(&mut self) -> Result { if self.pc + 4 > self.rom.len() { return Err("Unexpected end of ROM".into()); } let bytes = [ self.rom[self.pc], self.rom[self.pc + 1], self.rom[self.pc + 2], self.rom[self.pc + 3], ]; self.pc += 4; Ok(u32::from_le_bytes(bytes)) } fn read_u16(&mut self) -> Result { if self.pc + 2 > self.rom.len() { return Err("Unexpected end of ROM".into()); } let bytes = [ self.rom[self.pc], self.rom[self.pc + 1], ]; self.pc += 2; Ok(u16::from_le_bytes(bytes)) } pub fn push(&mut self, val: Value) { self.operand_stack.push(val); } pub fn pop(&mut self) -> Result { self.operand_stack.pop().ok_or("Stack underflow".into()) } pub fn pop_number(&mut self) -> Result { let val = self.pop()?; val.as_float().ok_or_else(|| "Expected number".into()) } pub fn pop_integer(&mut self) -> Result { let val = self.pop()?; val.as_integer().ok_or_else(|| "Expected integer".into()) } pub fn peek(&self) -> Result<&Value, String> { self.operand_stack.last().ok_or("Stack underflow".into()) } fn binary_op(&mut self, f: F) -> Result<(), String> where F: FnOnce(Value, Value) -> Result, { let b = self.pop()?; let a = self.pop()?; let res = f(a, b)?; self.push(res); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_add_instructions() { struct NoopNative; impl NativeInterface for NoopNative { fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } } let mut native = NoopNative; let mut rom = Vec::new(); rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // Const index 0 rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // Const index 1 rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); let constant_pool = vec![Value::Integer(10), Value::Integer(20)]; let mut vm = VirtualMachine::new(rom, constant_pool); vm.run_budget(100, &mut native).unwrap(); assert_eq!(vm.operand_stack.len(), 1); assert_eq!(vm.operand_stack[0], Value::Integer(30)); assert!(vm.halted); } #[test] fn test_jump_and_loop() { struct NoopNative; impl NativeInterface for NoopNative { fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } } let mut native = NoopNative; let mut rom = Vec::new(); // Index 0: PUSH 0 (counter) rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // Index 6: DUP let loop_start = rom.len(); rom.extend_from_slice(&(OpCode::Dup as u16).to_le_bytes()); // PUSH 10 rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // LT (counter < 10) rom.extend_from_slice(&(OpCode::Lt as u16).to_le_bytes()); // JMP_IF_FALSE to end rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); let jmp_placeholder = rom.len(); rom.extend_from_slice(&0u32.to_le_bytes()); // PUSH 1 rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&2u32.to_le_bytes()); // ADD (increment counter) rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); // JMP to start rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); rom.extend_from_slice(&(loop_start as u32).to_le_bytes()); // End let loop_end = rom.len(); rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); // Patch JMP_IF_FALSE addr let end_addr_bytes = (loop_end as u32).to_le_bytes(); rom[jmp_placeholder..jmp_placeholder+4].copy_from_slice(&end_addr_bytes); let constant_pool = vec![Value::Integer(0), Value::Integer(10), Value::Integer(1)]; let mut vm = VirtualMachine::new(rom, constant_pool); vm.run_budget(1000, &mut native).unwrap(); assert!(vm.halted); // O valor final na pilha deve ser 10 assert_eq!(vm.operand_stack.last(), Some(&Value::Integer(10))); } #[test] fn test_native_call_gfx_clear() { struct MockGfx { cleared_color: Option, } impl NativeInterface for MockGfx { fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result { if id == 0x1001 { let color = vm.pop_integer()? as usize; self.cleared_color = Some(color); return Ok(100); } Ok(0) } } let mut native = MockGfx { cleared_color: None }; let mut rom = Vec::new(); // PUSH 5 (color index) rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); rom.extend_from_slice(&0u32.to_le_bytes()); // CALL_NATIVE 0x1001 rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x1001u32.to_le_bytes()); rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); let constant_pool = vec![Value::Integer(5)]; let mut vm = VirtualMachine::new(rom, constant_pool); vm.run_budget(1000, &mut native).unwrap(); assert!(vm.halted); assert_eq!(native.cleared_color, Some(5)); } #[test] fn test_system_run_cart() { use crate::Machine; use crate::model::Cartridge; let mut machine = Machine::new(); // 1. Verifica que não tem cartucho inicialmente let mut rom = Vec::new(); // CALL_NATIVE 0x0001 (has_cart) rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); rom.extend_from_slice(&0x0001u32.to_le_bytes()); rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); machine.vm = VirtualMachine::new(rom, vec![]); let mut vm = std::mem::take(&mut machine.vm); vm.run_budget(100, &mut machine).unwrap(); machine.vm = vm; assert_eq!(machine.vm.pop().unwrap(), Value::Boolean(false)); // 2. Adiciona um cartucho e roda let mut cart_rom = Vec::new(); // PUSH_CONST 0 cart_rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); cart_rom.extend_from_slice(&0u32.to_le_bytes()); cart_rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); let cart_pool = vec![Value::Integer(42)]; let cart = Cartridge::new(cart_rom, cart_pool); machine.load_cartridge(cart); // Código para rodar o cartucho let mut boot_rom = Vec::new(); // CALL_NATIVE 0x0002 (run_cart) boot_rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); boot_rom.extend_from_slice(&0x0002u32.to_le_bytes()); machine.vm = VirtualMachine::new(boot_rom, vec![]); let mut vm = std::mem::take(&mut machine.vm); vm.run_budget(1000, &mut machine).unwrap(); machine.vm = vm; // Após o run_budget, a VM deve ter executado o cartucho assert_eq!(machine.vm.operand_stack.last(), Some(&Value::Integer(42))); assert!(machine.vm.halted); } }