diff options
Diffstat (limited to 'src/forth/interp.rs')
| -rw-r--r-- | src/forth/interp.rs | 532 |
1 files changed, 0 insertions, 532 deletions
diff --git a/src/forth/interp.rs b/src/forth/interp.rs deleted file mode 100644 index 512c022..0000000 --- a/src/forth/interp.rs +++ /dev/null @@ -1,532 +0,0 @@ -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<usize>), // true word index, optional false word index. - TIf(usize, Option<usize>), // 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<OpCode>); -impl std::ops::Deref for ByteCode { - type Target = Vec<OpCode>; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -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 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<i32>); - -#[derive(Debug)] -pub struct CallStack(pub Vec<InstructionPointer>); - -#[derive(Clone, Debug)] -pub struct WordList(pub Vec<ByteCode>); -impl std::ops::Deref for WordList { - type Target = Vec<ByteCode>; - 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<bool, 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) => 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<usize, RuntimeError> { - 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(()) - } -} |
