use std::collections::HashMap; 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, // 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(Debug)] pub(super) struct ByteCode(pub(super) Vec); 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(super) struct InstructionPointer { pub(super) word: usize, pub(super) offset: usize, } impl InstructionPointer { pub fn new() -> Self { Self { word: 0, offset: 0, } } } #[derive(Debug)] pub(super) struct DataStack(pub(super) Vec); #[derive(Debug)] pub(super) struct CallStack(pub(super) Vec); #[derive(Debug)] pub(super) struct WordList(pub(super) Vec); #[derive(Debug)] pub(super) struct WordCatalog<'a>(pub(super) HashMap<&'a str, usize>); #[derive(Debug)] pub struct Interp { stack: DataStack, callstack: CallStack, wordlist: WordList, ip: InstructionPointer, } #[derive(Debug)] pub enum RuntimeError { StackUnderflow, } impl Interp { pub fn new() -> Self { Self { stack: DataStack(Vec::new()), callstack: CallStack(Vec::new()), wordlist: WordList(vec![]), ip: InstructionPointer::new(), } } 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); } pub fn tick(&mut self) -> Result<(), RuntimeError> { let bc = &self.wordlist.0[self.ip.word]; eprintln!("debug: executing {:?}", bc[self.ip.offset]); match bc[self.ip.offset] { OpCode::Num(n) => self.stack.0.push(n), OpCode::Str(start, end) => eprintln!("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::Ret => { self.ip = self.callstack.0.pop().ok_or(RuntimeError::StackUnderflow)?; }, OpCode::Call(i) => { eprintln!("should call word based on index {}", i); self.call(i); // skip the offset increment return Ok(()) }, OpCode::TCall(i) => { eprintln!("should jump to word based on index {}", i); self.jmp(i); // skip the offset increment return Ok(()) }, OpCode::If(true_clause, false_clause) => { let flag = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; if flag != 0 { eprintln!("should call true branch {}", true_clause); self.call(true_clause); return Ok(()) } else if let Some(false_clause) = false_clause { eprintln!("should call false branch {}", false_clause); self.call(false_clause); return Ok(()) } else { eprintln!("should false fall through"); } }, OpCode::TIf(true_clause, false_clause) => { let flag = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; if flag != 0 { eprintln!("should call true branch {}", true_clause); self.jmp(true_clause); return Ok(()) } else if let Some(false_clause) = false_clause { eprintln!("should call false branch {}", false_clause); self.jmp(false_clause); return Ok(()) } else { eprintln!("should false fall through"); } }, } self.ip.offset += 1; Ok(()) } } #[cfg(test)] mod tests { use super::*; use super::OpCode; #[test] fn simple_ticks() -> Result<(), RuntimeError> { let mut interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Num(3), OpCode::Add])); 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()?; 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![ OpCode::Num(2), OpCode::Num(3), OpCode::Call(1), OpCode::Num(-2), OpCode::Add, OpCode::Sub, OpCode::Ret, ])); // "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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Call(1)])); interp.wordlist.0.push(ByteCode(vec![OpCode::TCall(2), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(-1), OpCode::If(1, None), OpCode::Num(0)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(0), OpCode::If(1, None), OpCode::Num(-1)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(-1), OpCode::If(1, Some(2)), OpCode::Num(-2)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(0), OpCode::If(1, Some(2)), OpCode::Num(-2)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(0)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(-1), OpCode::TIf(2, None), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(0)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(0), OpCode::TIf(2, None), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(-2)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(-1), OpCode::TIf(2, Some(3)), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); 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 interp = Interp::new(); interp.wordlist.0.push(ByteCode(vec![OpCode::Call(1), OpCode::Num(-2)])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(0), OpCode::TIf(2, Some(3)), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(1), OpCode::Ret])); interp.wordlist.0.push(ByteCode(vec![OpCode::Num(2), OpCode::Ret])); 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(()) } }