use log::debug; use std::ops::Index; #[derive(Clone, Debug, PartialEq)] pub enum OpCode { Num(i32), 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, EQ, GT, GTE, LT, LTE, // 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, } #[derive(Clone, Debug)] 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() } } 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, } } } #[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 Interp { // todo: don't be pub, probably pub stack: DataStack, pub callstack: CallStack, pub wordlist: WordList, pub ip: InstructionPointer, } #[derive(Debug)] pub enum RuntimeError { StackUnderflow, } impl Interp { pub fn new(wordlist: WordList) -> Self { Self { stack: DataStack(Vec::new()), callstack: CallStack(Vec::new()), ip: InstructionPointer::new(), wordlist, } } 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: {} to {}", start, 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 n = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.stack.0.push(n); self.stack.0.push(n); }, OpCode::Drop => { self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; }, 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::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) } }, } 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::*; use super::OpCode; #[test] fn simple_ticks() -> Result<(), RuntimeError> { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Num(3), OpCode::Add])); let mut interp = Interp::new(wordlist); assert_eq!(interp.tick()?, true, "first arg running"); assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 1); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], 2, "first argument"); assert_eq!(interp.tick()?, true, "second arg running"); assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 2); assert_eq!(interp.stack.0.len(), 2); assert_eq!(interp.stack.0[1], 3, "second argument"); assert_eq!(interp.tick()?, false, "stopped after addition"); assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 3); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], 5, "result of addition"); Ok(()) } #[test] fn custom_word() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); // "sub" definition interp.wordlist.0.push(ByteCode(vec![ OpCode::Sub, OpCode::Ret, ])); interp.tick()?; assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 1); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], 2, "first argument"); interp.tick()?; assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 2); assert_eq!(interp.stack.0.len(), 2); assert_eq!(interp.stack.0[1], 3, "second argument"); interp.tick()?; // call sub assert_eq!(interp.ip.word, 1); assert_eq!(interp.ip.offset, 0); assert_eq!(interp.stack.0.len(), 2); interp.tick()?; // - assert_eq!(interp.ip.word, 1); assert_eq!(interp.ip.offset, 1); assert_eq!(interp.stack.0.len(), 1); interp.tick()?; // ret assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 3); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], -1, "result of sub word"); interp.tick()?; // 2 assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 4); assert_eq!(interp.stack.0.len(), 2); assert_eq!(interp.stack.0[1], -2, "post sub arg"); interp.tick()?; // - assert_eq!(interp.ip.word, 0); assert_eq!(interp.ip.offset, 5); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], -3, "add opcode result"); Ok(()) } #[test] fn tail_call() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; // call assert_eq!(interp.callstack.0.len(), 1, "callstack after call"); interp.tick()?; // tcall assert_eq!(interp.callstack.0.len(), 1, "callstack after tcall"); interp.tick()?; // ret assert_eq!(interp.callstack.0.len(), 0, "callstack after ret"); Ok(()) } #[test] fn if_then_true() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push -1 stack len"); assert_eq!(interp.stack.0[0], -1, "push -1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "if stack "); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 1 stack len"); assert_eq!(interp.stack.0[0], 1, "push 1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret stack len"); assert_eq!(interp.stack.0[0], 1, "ret stack val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 2, "push 0 stack len"); assert_eq!(interp.stack.0[1], 0, "push 0 stack val"); Ok(()) } #[test] fn if_then_false() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 0 stack len"); assert_eq!(interp.stack.0[0], 0, "push 0 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "if stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push -1 stack len"); assert_eq!(interp.stack.0[0], -1, "push -1 val"); Ok(()) } #[test] fn if_else_then_true() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push -1 stack len"); assert_eq!(interp.stack.0[0], -1, "push -1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "if stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 1 stack len"); assert_eq!(interp.stack.0[0], 1, "push 1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret stack len"); assert_eq!(interp.stack.0[0], 1, "ret val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 2, "push -2 stack len"); assert_eq!(interp.stack.0[1], -2, "push -2 val"); Ok(()) } #[test] fn if_else_then_false() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 0 stack len"); assert_eq!(interp.stack.0[0], 0, "push 0 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "if"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 2 stack len"); assert_eq!(interp.stack.0[0], 2, "push 2 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret"); assert_eq!(interp.stack.0[0], 2, "ret"); interp.tick()?; assert_eq!(interp.stack.0.len(), 2, "push -2 stack len"); assert_eq!(interp.stack.0[1], -2, "push -2 val"); Ok(()) } #[test] fn tail_if_then_true() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "call(1) stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push -1 stack len"); assert_eq!(interp.stack.0[0], -1, "push -1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "tif stack "); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 1 stack len"); assert_eq!(interp.stack.0[0], 1, "push 1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret stack len"); assert_eq!(interp.stack.0[0], 1, "ret stack val"); interp.tick()?; // no ret on wordlist[1] since it's ‘tif’ assert_eq!(interp.stack.0.len(), 2, "push 0 stack len"); assert_eq!(interp.stack.0[1], 0, "push 0 stack val"); Ok(()) } #[test] fn tail_if_then_false() -> Result<(), RuntimeError> { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(0)])); wordlist.0.push(ByteCode(vec![OpCode::Num(0), OpCode::TIf(2, None), OpCode::Ret])); wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); let mut interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "call(1) stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 0 stack len"); assert_eq!(interp.stack.0[0], 0, "push 0 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "tif stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "ret stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 0 stack len"); assert_eq!(interp.stack.0[0], 0, "push 0 val"); Ok(()) } #[test] fn tail_if_else_then_true() -> Result<(), RuntimeError> { 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "call(1) stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push -1 stack len"); assert_eq!(interp.stack.0[0], -1, "push -1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "tif stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 1 stack len"); assert_eq!(interp.stack.0[0], 1, "push 1 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret stack len"); assert_eq!(interp.stack.0[0], 1, "ret val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 2, "push -2 stack len"); assert_eq!(interp.stack.0[1], -2, "push -2 val"); Ok(()) } #[test] fn tail_if_else_then_false() -> Result<(), RuntimeError> { let mut wordlist = WordList(vec![]); wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(-2)])); wordlist.0.push(ByteCode(vec![OpCode::Num(0), 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 interp = Interp::new(wordlist); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "call(1) stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 0 stack len"); assert_eq!(interp.stack.0[0], 0, "push 0 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 0, "tif stack len"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "push 2 stack len"); assert_eq!(interp.stack.0[0], 2, "push 2 val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 1, "ret stack len"); assert_eq!(interp.stack.0[0], 2, "ret val"); interp.tick()?; assert_eq!(interp.stack.0.len(), 2, "push -2 stack len"); assert_eq!(interp.stack.0[1], -2, "push -2 val"); Ok(()) } }