use std::collections::HashMap; use std::ops::Index; #[derive(Clone, Debug, PartialEq)] pub enum OpCode { Num(i32), Str(usize, usize), Call(usize), 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] } } // .0 is wordlist entry's bytecode, .1 is index into that bytecode #[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 tick(&mut self) -> Result<(), RuntimeError> { 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) => 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 jump to word based on index {}", i); self.callstack.0.push(self.ip); self.ip.word = i; self.ip.offset = 0; // skip the offset increment return Ok(()) } } 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(()) } }