diff options
Diffstat (limited to 'src/forth/interp.rs')
| -rw-r--r-- | src/forth/interp.rs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/src/forth/interp.rs b/src/forth/interp.rs new file mode 100644 index 0000000..2ade22d --- /dev/null +++ b/src/forth/interp.rs @@ -0,0 +1,175 @@ +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<OpCode>); + +impl ByteCode { + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl Index<usize> 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<i32>); + +#[derive(Debug)] +pub(super) struct CallStack(pub(super) Vec<InstructionPointer>); + +#[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(()) + } +} |
