use std::collections::HashMap; use std::ops::Index; #[derive(Clone, Debug, PartialEq)] pub enum OpCode { Num(i32), Str(usize, usize), WordI(usize), Word(&'static str), Add, Sub, 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) usize); #[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) HashMap<&'static str, InstructionPointer>); #[derive(Debug)] pub(super) struct WordCatalog<'a>(pub(super) HashMap<&'a str, usize>); #[derive(Debug)] pub struct Interp { stack: DataStack, callstack: CallStack, wordlist: WordList, bytecode: ByteCode, ip: InstructionPointer, } #[derive(Debug)] pub enum RuntimeError { StackUnderflow, UndefinedWord, } impl Interp { pub fn new() -> Self { Self { stack: DataStack(Vec::new()), callstack: CallStack(Vec::new()), wordlist: WordList(HashMap::new()), bytecode: ByteCode(Vec::new()), ip: InstructionPointer(0), } } pub fn tick(&mut self) -> Result<(), RuntimeError> { match self.bytecode[self.ip.0] { 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 => { let ip = self.callstack.0.pop().ok_or(RuntimeError::StackUnderflow)?; self.ip = ip; }, OpCode::Word(w) => { let ip = self.wordlist.0.get(w).ok_or(RuntimeError::UndefinedWord)?; self.callstack.0.push(self.ip); self.ip.0 = ip.0 - 1; // we auto-increment at the end }, OpCode::WordI(i) => { eprintln!("should jump to word based on index {}", i); } } self.ip.0 += 1; Ok(()) } } #[cfg(test)] mod tests { use super::*; use super::OpCode; #[test] fn simple_ticks() -> Result<(), RuntimeError> { let mut interp = Interp::new(); interp.bytecode = ByteCode(vec![OpCode::Num(2), OpCode::Num(3), OpCode::Add]); interp.tick()?; assert_eq!(interp.ip.0, 1); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], 2, "first argument"); interp.tick()?; assert_eq!(interp.ip.0, 2); assert_eq!(interp.stack.0.len(), 2); assert_eq!(interp.stack.0[1], 3, "second argument"); interp.tick()?; assert_eq!(interp.ip.0, 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.bytecode = ByteCode(vec![ OpCode::Num(2), OpCode::Num(3), OpCode::Word("sub"), OpCode::Num(-2), OpCode::Add, // "sub" definition OpCode::Sub, OpCode::Ret, ]); // 5 is offset of w interp.wordlist.0.insert("sub", InstructionPointer(5)); interp.tick()?; assert_eq!(interp.ip.0, 1); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], 2, "first argument"); interp.tick()?; assert_eq!(interp.ip.0, 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.0, 5); assert_eq!(interp.stack.0.len(), 2); interp.tick()?; // - assert_eq!(interp.ip.0, 6); assert_eq!(interp.stack.0.len(), 1); interp.tick()?; // ret assert_eq!(interp.ip.0, 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.0, 4); assert_eq!(interp.stack.0.len(), 2); assert_eq!(interp.stack.0[1], -2, "post sub arg"); interp.tick()?; // - assert_eq!(interp.ip.0, 5); assert_eq!(interp.stack.0.len(), 1); assert_eq!(interp.stack.0[0], -3, "add opcode result"); Ok(()) } }