summaryrefslogtreecommitdiffstats
path: root/src/forth/interp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/forth/interp.rs')
-rw-r--r--src/forth/interp.rs175
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(())
+ }
+}