diff --git a/crates/core/src/vm/opcode.rs b/crates/core/src/vm/opcode.rs index 218c3a4b..54860422 100644 --- a/crates/core/src/vm/opcode.rs +++ b/crates/core/src/vm/opcode.rs @@ -130,4 +130,28 @@ impl OpCode { OpCode::FrameSync => 1, } } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_opcode_decoding() { + assert_eq!(OpCode::try_from(0x00).unwrap(), OpCode::Nop); + assert_eq!(OpCode::try_from(0x10).unwrap(), OpCode::PushConst); + assert_eq!(OpCode::try_from(0x20).unwrap(), OpCode::Add); + assert_eq!(OpCode::try_from(0x70).unwrap(), OpCode::Syscall); + assert_eq!(OpCode::try_from(0x80).unwrap(), OpCode::FrameSync); + assert!(OpCode::try_from(0xFFFF).is_err()); + } + + #[test] + fn test_opcode_cycles() { + assert_eq!(OpCode::Nop.cycles(), 1); + assert_eq!(OpCode::Add.cycles(), 2); + assert_eq!(OpCode::Mul.cycles(), 4); + assert_eq!(OpCode::Div.cycles(), 6); + assert_eq!(OpCode::Alloc.cycles(), 10); + } } \ No newline at end of file diff --git a/crates/core/src/vm/value.rs b/crates/core/src/vm/value.rs index 92fba080..5d23e591 100644 --- a/crates/core/src/vm/value.rs +++ b/crates/core/src/vm/value.rs @@ -40,4 +40,38 @@ impl Value { _ => None, } } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_value_equality() { + assert_eq!(Value::Integer(10), Value::Integer(10)); + assert_eq!(Value::Float(10.5), Value::Float(10.5)); + assert_eq!(Value::Integer(10), Value::Float(10.0)); + assert_eq!(Value::Float(10.0), Value::Integer(10)); + assert_ne!(Value::Integer(10), Value::Integer(11)); + assert_ne!(Value::Integer(10), Value::Float(10.1)); + assert_eq!(Value::Boolean(true), Value::Boolean(true)); + assert_ne!(Value::Boolean(true), Value::Boolean(false)); + assert_eq!(Value::String("oi".into()), Value::String("oi".into())); + assert_eq!(Value::Null, Value::Null); + } + + #[test] + fn test_value_conversions() { + let v_int = Value::Integer(42); + assert_eq!(v_int.as_float(), Some(42.0)); + assert_eq!(v_int.as_integer(), Some(42)); + + let v_float = Value::Float(42.7); + assert_eq!(v_float.as_float(), Some(42.7)); + assert_eq!(v_float.as_integer(), Some(42)); + + let v_bool = Value::Boolean(true); + assert_eq!(v_bool.as_float(), None); + assert_eq!(v_bool.as_integer(), None); + } } \ No newline at end of file diff --git a/crates/core/src/vm/virtual_machine.rs b/crates/core/src/vm/virtual_machine.rs index cabc701b..247cda45 100644 --- a/crates/core/src/vm/virtual_machine.rs +++ b/crates/core/src/vm/virtual_machine.rs @@ -634,4 +634,84 @@ mod tests { assert_eq!(machine.vm.operand_stack.last(), Some(&Value::Integer(42))); assert!(machine.vm.halted); } + + #[test] + fn test_stack_operations() { + struct NoopNative; + impl NativeInterface for NoopNative { + fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } + } + let mut native = NoopNative; + + let mut rom = Vec::new(); + // PUSH 10, PUSH 20, SWAP, POP + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Swap as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Pop 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(20)); + } + + #[test] + fn test_logical_operations() { + struct NoopNative; + impl NativeInterface for NoopNative { + fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } + } + let mut native = NoopNative; + + let mut rom = Vec::new(); + // PUSH true, NOT (-> false), PUSH true, OR (-> true) + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // true + rom.extend_from_slice(&(OpCode::Not as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // true + rom.extend_from_slice(&(OpCode::Or as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let constant_pool = vec![Value::Boolean(true)]; + let mut vm = VirtualMachine::new(rom, constant_pool); + vm.run_budget(100, &mut native).unwrap(); + + assert_eq!(vm.operand_stack.last(), Some(&Value::Boolean(true))); + } + + #[test] + fn test_scopes() { + struct NoopNative; + impl NativeInterface for NoopNative { + fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } + } + let mut native = NoopNative; + + let mut rom = Vec::new(); + // PUSH_SCOPE 2 (reserves 2 nulls), PUSH 42, SET_LOCAL 0, GET_LOCAL 0, POP_SCOPE + rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes()); + rom.extend_from_slice(&2u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // 42 + rom.extend_from_slice(&(OpCode::SetLocal as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::GetLocal as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::PopScope as u16).to_le_bytes()); + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let constant_pool = vec![Value::Integer(42)]; + let mut vm = VirtualMachine::new(rom, constant_pool); + vm.run_budget(100, &mut native).unwrap(); + + // Stack should be empty because POP_SCOPE truncates to stack_base + assert_eq!(vm.operand_stack.len(), 0); + } }