use log::debug; use std::ops::Index; pub type DataStackType = isize; const MIN_SPEED: DataStackType = 0; const MAX_SPEED: DataStackType = 10; const UNITS_PER_ROTATION: DataStackType = 360; #[derive(Clone, Debug, Eq, PartialEq)] pub enum OpCode { Num(DataStackType), Str(usize, usize), Call(usize), TCall(usize), // tail call, really just ‘jmp’, but named to indicate desired usage. If(usize, Option), // true word index, optional false word index. TIf(usize, Option), // tail version Add, Sub, Mul, Div, Dup, Drop, Swap, Rot, EQ, GT, GTE, LT, LTE, Not, And, Or, Xor, // todo: maybe get rid of ret? it's only used in word definitions, // which are bounded, so running of the end can be treated as an // implicit ‘ret’. Ret, // robo-specific stuff // // todo: figure out how to split this out of the compiler/vm and // provide it as some kind of plugin. Say, Heading, SetHeading, Speed, SetSpeed, Doppler, } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ByteCode(pub Vec); impl std::ops::Deref for ByteCode { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl ByteCode { pub fn len(&self) -> usize { self.0.len() } pub fn is_empty(&self) -> bool { self.0.is_empty() } } // really just to make unit testing look nicer. should probably move // it there. impl PartialEq> for &ByteCode { fn eq(&self, other: &Vec) -> bool { self.0.len() == other.len() && std::iter::zip(&self.0, other).all(|(a, b)| a == b) } } impl Index for ByteCode { type Output = OpCode; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } #[derive(Debug, Copy, Clone, PartialEq)] pub struct InstructionPointer { pub word: usize, pub offset: usize, } impl InstructionPointer { pub fn new() -> Self { Self { word: 0, offset: 0 } } } impl Default for InstructionPointer { fn default() -> Self { Self::new() } } #[derive(Debug)] pub struct DataStack(pub Vec); #[derive(Debug)] pub struct CallStack(pub Vec); #[derive(Clone, Debug)] pub struct WordList(pub Vec); impl std::ops::Deref for WordList { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug)] pub struct VM { // todo: don't be pub, probably pub stack: DataStack, pub callstack: CallStack, pub wordlist: WordList, pub ip: InstructionPointer, pub out: Vec, pub heading: DataStackType, pub speed: DataStackType, pub doppler: DataStackType, } #[derive(Debug, PartialEq, Eq)] pub enum RuntimeError { StackUnderflow, } impl std::fmt::Display for RuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::StackUnderflow => write!(f, "stack underflow"), } } } impl std::error::Error for RuntimeError {} impl VM { pub fn new(wordlist: WordList) -> Self { Self { stack: DataStack(Vec::new()), callstack: CallStack(Vec::new()), ip: InstructionPointer::new(), wordlist, out: vec![], heading: 0, speed: 0, doppler: 0, } } pub fn jmp(&mut self, word: usize) { self.ip.word = word; self.ip.offset = 0; } pub fn call(&mut self, word: usize) { self.callstack.0.push(self.ip); self.jmp(word); } // bool is still_running pub fn tick(&mut self) -> Result { let bc = &self.wordlist.0[self.ip.word]; match bc[self.ip.offset] { OpCode::Num(n) => self.stack.0.push(n), OpCode::Str(start, end) => debug!("got str: {start} to {end}"), OpCode::Add => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.stack.0.push(n2 + n1); } OpCode::Sub => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.stack.0.push(n2 - n1); } OpCode::Mul => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.stack.0.push(n2 * n1); } OpCode::Div => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.stack.0.push(n2 / n1); } OpCode::Dup => { let len = self.stack.0.len(); if len < 1 { return Err(RuntimeError::StackUnderflow); } self.stack.0.push(self.stack.0[len - 1]); } OpCode::Drop => { self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; } OpCode::Swap => { let top = self.stack.0.len() - 1; if top < 1 { return Err(RuntimeError::StackUnderflow); } let (a, b) = self.stack.0.split_at_mut(top); std::mem::swap(&mut a[top - 1], &mut b[0]); } OpCode::Rot => { let top = self.stack.0.len() - 1; if top < 2 { return Err(RuntimeError::StackUnderflow); } let (a, b) = self.stack.0.split_at_mut(top); std::mem::swap(&mut a[top - 1], &mut b[0]); std::mem::swap(&mut a[top - 2], &mut b[0]); } OpCode::EQ => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n2 == n1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::GT => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n2 > n1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::GTE => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n2 >= n1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::LT => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n2 < n1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::LTE => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n2 <= n1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::Not => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n1 == 0 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::And => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n1 == 0 || n2 == 0 { 0 } else { -1 }; self.stack.0.push(v); } OpCode::Or => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if n1 == -1 || n2 == -1 { -1 } else { 0 }; self.stack.0.push(v); } OpCode::Xor => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let v = if (n1 == 0 && n2 != 0) || (n1 != 0 && n2 == 0) { -1 } else { 0 }; self.stack.0.push(v); } OpCode::Ret => { self.ip = self.callstack.0.pop().ok_or(RuntimeError::StackUnderflow)?; } OpCode::Call(i) => { self.call(i); // skip the offset increment return Ok(true); } OpCode::TCall(i) => { self.jmp(i); // skip the offset increment return Ok(true); } OpCode::If(true_clause, false_clause) => { let flag = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; if flag != 0 { self.call(true_clause); return Ok(true); } else if let Some(false_clause) = false_clause { self.call(false_clause); return Ok(true); } } OpCode::TIf(true_clause, false_clause) => { let flag = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; if flag != 0 { self.jmp(true_clause); return Ok(true); } else if let Some(false_clause) = false_clause { self.jmp(false_clause); return Ok(true); } else { self.ip = self.callstack.0.pop().ok_or(RuntimeError::StackUnderflow)?; } } OpCode::Say => { let v = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.out.push(format!("{}", v)); } OpCode::Heading => { self.stack.0.push(self.heading); } OpCode::SetHeading => { let v = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.heading = (UNITS_PER_ROTATION + (v % UNITS_PER_ROTATION)) % UNITS_PER_ROTATION; } OpCode::Speed => { self.stack.0.push(self.speed); } OpCode::SetSpeed => { let v = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.speed = if v < MIN_SPEED { MIN_SPEED } else if v > MAX_SPEED { MAX_SPEED } else { v }; } OpCode::Doppler => { self.stack.0.push(self.doppler); } } self.ip.offset += 1; Ok(self.ip.offset < self.wordlist.0[self.ip.word].len()) } pub fn run(&mut self) -> Result { let mut count = 0; while self.tick()? { count += 1 } Ok(count) } } #[cfg(test)] mod tests { use super::OpCode; use super::*; fn wl_for(v: Vec>) -> WordList { WordList(v.into_iter().map(|x| ByteCode(x)).collect()) } fn op_from(v: bool) -> OpCode { if v { OpCode::Num(-1) } else { OpCode::Num(0) } } #[test] fn simple_ticks() { let mut wordlist = WordList(vec![]); wordlist .0 .push(ByteCode(vec![OpCode::Num(2), OpCode::Num(3), OpCode::Add])); let mut vm = VM::new(wordlist); assert_eq!( vm.tick().expect("should execute instruction"), true, "first arg running" ); assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 1); assert_eq!(vm.stack.0.len(), 1); assert_eq!(vm.stack.0[0], 2, "first argument"); assert_eq!( vm.tick().expect("should execute instruction"), true, "second arg running" ); assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 2); assert_eq!(vm.stack.0.len(), 2); assert_eq!(vm.stack.0[1], 3, "second argument"); assert_eq!( vm.tick().expect("should execute instruction"), false, "stopped after addition" ); assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 3); assert_eq!(vm.stack.0.len(), 1); assert_eq!(vm.stack.0[0], 5, "result of addition"); } #[test] fn custom_word() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![ OpCode::Num(2), OpCode::Num(3), OpCode::Call(1), OpCode::Num(-2), OpCode::Add, OpCode::Sub, OpCode::Ret, ])); let mut vm = VM::new(wordlist); // "sub" definition vm.wordlist.0.push(ByteCode(vec![OpCode::Sub, OpCode::Ret])); vm.tick().expect("should execute instruction"); assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 1); assert_eq!(vm.stack.0.len(), 1); assert_eq!(vm.stack.0[0], 2, "first argument"); vm.tick().expect("should execute instruction"); assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 2); assert_eq!(vm.stack.0.len(), 2); assert_eq!(vm.stack.0[1], 3, "second argument"); vm.tick().expect("should execute instruction"); // call sub assert_eq!(vm.ip.word, 1); assert_eq!(vm.ip.offset, 0); assert_eq!(vm.stack.0.len(), 2); vm.tick().expect("should execute instruction"); // - assert_eq!(vm.ip.word, 1); assert_eq!(vm.ip.offset, 1); assert_eq!(vm.stack.0.len(), 1); vm.tick().expect("should execute instruction"); // ret assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 3); assert_eq!(vm.stack.0.len(), 1); assert_eq!(vm.stack.0[0], -1, "result of sub word"); vm.tick().expect("should execute instruction"); // 2 assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 4); assert_eq!(vm.stack.0.len(), 2); assert_eq!(vm.stack.0[1], -2, "post sub arg"); vm.tick().expect("should execute instruction"); // - assert_eq!(vm.ip.word, 0); assert_eq!(vm.ip.offset, 5); assert_eq!(vm.stack.0.len(), 1); assert_eq!(vm.stack.0[0], -3, "add opcode result"); } #[test] fn tail_call() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![OpCode::Call(1)])); wordlist .0 .push(ByteCode(vec![OpCode::TCall(2), OpCode::Ret])); wordlist.0.push(ByteCode(vec![OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); // call assert_eq!(vm.callstack.0.len(), 1, "callstack after call"); vm.tick().expect("should execute instruction"); // tcall assert_eq!(vm.callstack.0.len(), 1, "callstack after tcall"); vm.tick().expect("should execute instruction"); // ret assert_eq!(vm.callstack.0.len(), 0, "callstack after ret"); } #[test] fn if_then_true() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![ OpCode::Num(-1), OpCode::If(1, None), OpCode::Num(0), ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push -1 stack len"); assert_eq!(vm.stack.0[0], -1, "push -1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "if stack "); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 1 stack len"); assert_eq!(vm.stack.0[0], 1, "push 1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "ret stack len"); assert_eq!(vm.stack.0[0], 1, "ret stack val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 2, "push 0 stack len"); assert_eq!(vm.stack.0[1], 0, "push 0 stack val"); } #[test] fn if_then_false() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![ OpCode::Num(0), OpCode::If(1, None), OpCode::Num(-1), ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 0 stack len"); assert_eq!(vm.stack.0[0], 0, "push 0 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "if stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push -1 stack len"); assert_eq!(vm.stack.0[0], -1, "push -1 val"); } #[test] fn if_else_then_true() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![ OpCode::Num(-1), OpCode::If(1, Some(2)), OpCode::Num(-2), ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push -1 stack len"); assert_eq!(vm.stack.0[0], -1, "push -1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "if stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 1 stack len"); assert_eq!(vm.stack.0[0], 1, "push 1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "ret stack len"); assert_eq!(vm.stack.0[0], 1, "ret val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 2, "push -2 stack len"); assert_eq!(vm.stack.0[1], -2, "push -2 val"); } #[test] fn if_else_then_false() { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![ OpCode::Num(0), OpCode::If(1, Some(2)), OpCode::Num(-2), ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 0 stack len"); assert_eq!(vm.stack.0[0], 0, "push 0 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "if"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 2 stack len"); assert_eq!(vm.stack.0[0], 2, "push 2 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "ret"); assert_eq!(vm.stack.0[0], 2, "ret"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 2, "push -2 stack len"); assert_eq!(vm.stack.0[1], -2, "push -2 val"); } #[test] fn tail_if_then_true() { let mut wordlist = WordList(vec![]); wordlist .0 .push(ByteCode(vec![OpCode::Call(1), OpCode::Num(0)])); wordlist.0.push(ByteCode(vec![ OpCode::Num(-1), OpCode::TIf(2, None), OpCode::Ret, ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "call(1) stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push -1 stack len"); assert_eq!(vm.stack.0[0], -1, "push -1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "tif stack "); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 1 stack len"); assert_eq!(vm.stack.0[0], 1, "push 1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "ret stack len"); assert_eq!(vm.stack.0[0], 1, "ret stack val"); vm.tick().expect("should execute instruction"); // no ret on wordlist[1] since it's ‘tif’ assert_eq!(vm.stack.0.len(), 2, "push 0 stack len"); assert_eq!(vm.stack.0[1], 0, "push 0 stack val"); } #[test] fn tail_if_then_false() { let wordlist = wl_for(vec![ vec![OpCode::Call(1), OpCode::Num(0)], vec![OpCode::Num(0), OpCode::TIf(2, None)], vec![OpCode::Num(1), OpCode::Ret], ]); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "call(1) stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![0], "push 0 stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![], "tif stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![0], "push 0 stack"); } #[test] fn tail_if_else_then_true() { let mut wordlist = WordList(vec![]); wordlist .0 .push(ByteCode(vec![OpCode::Call(1), OpCode::Num(-2)])); wordlist.0.push(ByteCode(vec![ OpCode::Num(-1), OpCode::TIf(2, Some(3)), OpCode::Ret, ])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "call(1) stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push -1 stack len"); assert_eq!(vm.stack.0[0], -1, "push -1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 0, "tif stack len"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "push 1 stack len"); assert_eq!(vm.stack.0[0], 1, "push 1 val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 1, "ret stack len"); assert_eq!(vm.stack.0[0], 1, "ret val"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0.len(), 2, "push -2 stack len"); assert_eq!(vm.stack.0[1], -2, "push -2 val"); } #[test] fn tail_if_else_then_false() { let wordlist = WordList( vec![ vec![OpCode::Call(1), OpCode::Num(-2)], vec![OpCode::Num(0), OpCode::TIf(2, Some(3)), OpCode::Ret], vec![OpCode::Num(1), OpCode::Ret], vec![OpCode::Num(2), OpCode::Ret], ] .into_iter() .map(|x| ByteCode(x)) .collect(), ); let mut vm = VM::new(wordlist); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![], "call(1) stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![0], "push 0 stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![], "tif stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![2], "push 2 stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![2], "ret stack"); vm.tick().expect("should execute instruction"); assert_eq!(vm.stack.0, vec![2, -2], "push -2 stack"); } #[test] fn opcode_dup() { let wordlist = wl_for(vec![vec![OpCode::Num(1), OpCode::Dup]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![1, 1]); } #[test] fn opcode_dup_underflow() { let wordlist = wl_for(vec![vec![OpCode::Dup]]); let mut vm = VM::new(wordlist); assert!(vm.run().is_err_and(|e| e == RuntimeError::StackUnderflow)); } #[test] fn opcode_swap() { let wordlist = wl_for(vec![vec![OpCode::Num(1), OpCode::Num(2), OpCode::Swap]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![2, 1]); } #[test] fn opcode_swap_underflow() { let wordlist = wl_for(vec![vec![OpCode::Num(1), OpCode::Swap]]); let mut vm = VM::new(wordlist); assert!(vm.run().is_err_and(|e| e == RuntimeError::StackUnderflow)); } #[test] fn opcode_rot() { let wordlist = wl_for(vec![vec![ OpCode::Num(1), OpCode::Num(2), OpCode::Num(3), OpCode::Rot, ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![2, 3, 1]); } #[test] fn opcode_rot_underflow() { let wordlist = wl_for(vec![vec![OpCode::Num(1), OpCode::Num(2), OpCode::Rot]]); let mut vm = VM::new(wordlist); assert!(vm.run().is_err_and(|e| e == RuntimeError::StackUnderflow)); } #[test] fn tif_no_ret() { let wordlist = wl_for(vec![ vec![OpCode::Call(1)], vec![OpCode::Num(0), OpCode::TIf(2, None), OpCode::Num(3)], vec![OpCode::Num(-2)], ]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![]); } #[test] fn opcode_not() { let wordlist = wl_for(vec![vec![OpCode::Num(0), OpCode::Not]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![-1]); } #[test] fn opcode_and() { let tests = [ (true, true, -1), (false, false, 0), (true, false, 0), (false, true, 0), ]; for (a, b, res) in tests { let wordlist = wl_for(vec![vec![ op_from(a), op_from(b), OpCode::And ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![res]); } } #[test] fn opcode_or() { let tests = [ (true, true, -1), (false, false, 0), (true, false, -1), (false, true, -1), ]; for (a, b, res) in tests { let wordlist = wl_for(vec![vec![ op_from(a), op_from(b), OpCode::Or ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![res]); } } #[test] fn opcode_xor() { let tests = [ (true, true, 0), (false, false, 0), (true, false, -1), (false, true, -1), ]; for (a, b, res) in tests { let wordlist = wl_for(vec![vec![ op_from(a), op_from(b), OpCode::Xor ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0, vec![res]); } } #[test] fn opcode_say() { let wordlist = wl_for(vec![vec![ OpCode::Num(-1), OpCode::Say ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 0, "empty stack"); assert_eq!(vm.out.len(), 1, "output length"); assert_eq!(vm.out[0], "-1", "output value"); } #[test] fn opcode_heading() { let wordlist = wl_for(vec![vec![ OpCode::Heading ]]); let mut vm = VM::new(wordlist); vm.heading = 12; vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 1, "stack size"); assert_eq!(vm.stack.0[0], 12, "heading value"); } #[test] fn opcode_setheading() { let wordlist = wl_for(vec![vec![ OpCode::Num(3), OpCode::SetHeading ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 0, "empty stack"); assert_eq!(vm.heading, 3, "heading value"); } #[test] fn opcode_speed() { let wordlist = wl_for(vec![vec![ OpCode::Speed ]]); let mut vm = VM::new(wordlist); vm.speed = 12; vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 1, "stack size"); assert_eq!(vm.stack.0[0], 12, "speed value"); } #[test] fn opcode_setspeed() { let wordlist = wl_for(vec![vec![ OpCode::Num(3), OpCode::SetSpeed ]]); let mut vm = VM::new(wordlist); vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 0, "empty stack"); assert_eq!(vm.speed, 3, "speed value"); } #[test] fn opcode_doppler() { let wordlist = wl_for(vec![vec![ OpCode::Doppler ]]); let mut vm = VM::new(wordlist); vm.doppler = 12; vm.run().expect("should run to completion"); assert_eq!(vm.stack.0.len(), 1, "stack size"); assert_eq!(vm.stack.0[0], 12, "doppler value"); } }