diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/forth/compiler.rs | 98 | ||||
| -rw-r--r-- | src/forth/vm.rs | 41 | ||||
| -rwxr-xr-x | src/lib.rs | 31 |
3 files changed, 78 insertions, 92 deletions
diff --git a/src/forth/compiler.rs b/src/forth/compiler.rs index 9120506..17f0113 100644 --- a/src/forth/compiler.rs +++ b/src/forth/compiler.rs @@ -19,7 +19,7 @@ impl std::fmt::Display for CompileError { Self::DefStackEmpty => write!(f, "def stack empty"), Self::MissingIf => write!(f, "missing if"), Self::MissingQuote => write!(f, "missing ending quote"), - Self::UnknownWord(word) => write!(f, "unknown word: {}", word), + Self::UnknownWord(word) => write!(f, "unknown word: {word}"), } } } @@ -66,13 +66,11 @@ pub struct Compiler<'a> { impl<'a> Compiler<'a> { pub fn new(text: &'a str) -> Self { - let mut wl = vec![]; - // main routine is always the first entry. - wl.push(ByteCode(vec![])); Self { text, enumerator: text.chars().enumerate(), - wordlist: WordList(wl), + // we always need something at index 0 + wordlist: WordList(vec![ByteCode(vec![])]), annotations: vec![vec![]], wordalog: WordCatalog(HashMap::new()), defstack: vec![], @@ -127,7 +125,7 @@ impl<'a> Compiler<'a> { r#"s""# => { let (s_end, _) = self.enumerator - .find(|(_i, c)| return *c == '"') + .find(|(_i, c)| *c == '"') .ok_or(CompileError::MissingQuote)?; self.bc_push(OpCode::Str(end+1, s_end), anno); }, @@ -235,32 +233,28 @@ mod tests { fn literal_num() { let comp = compiler_for("1\n"); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Num(1)); + assert_eq!(main, vec![OpCode::Num(1)]); } #[test] fn literal_string() { let comp = compiler_for(r#"s" hello there""#); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Str(3, 14)); + assert_eq!(main, vec![OpCode::Str(3, 14)]); } #[test] fn add_opcode() { let comp = compiler_for("+\n"); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Add); + assert_eq!(main, vec![OpCode::Add]); } #[test] fn sub_opcode() { let comp = compiler_for("-\n"); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Sub); + assert_eq!(main, vec![OpCode::Sub]); } #[test] @@ -268,14 +262,10 @@ mod tests { let comp = compiler_for(": add2 2 + ; 3 add2\n"); let main = &comp.wordlist.0[0]; let add2_index = comp.wordalog.0.get("add2").expect("add2 has entry in wordlist"); + assert_eq!(main, vec![OpCode::Num(3), OpCode::Call(*add2_index)]); + let add2 = &comp.wordlist.0[*add2_index]; - assert_eq!(main.len(), 2); - assert_eq!(main[0], OpCode::Num(3)); - assert_eq!(main[1], OpCode::Call(*add2_index)); - assert_eq!(add2.len(), 3); - assert_eq!(add2[0], OpCode::Num(2)); - assert_eq!(add2[1], OpCode::Add); - assert_eq!(add2[2], OpCode::Ret); + assert_eq!(add2, vec![OpCode::Num(2), OpCode::Add, OpCode::Ret]); } #[test] @@ -284,14 +274,9 @@ mod tests { eprintwordlist(&comp.wordlist); assert_eq!(comp.wordlist.0.len(), 2); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::If(1, None)); + assert_eq!(main, vec![OpCode::If(1, None)]); let tr = &comp.wordlist.0[1]; - assert_eq!(tr.len(), 4); - assert_eq!(tr[0], OpCode::Num(1)); - assert_eq!(tr[1], OpCode::Num(2)); - assert_eq!(tr[2], OpCode::Add); - assert_eq!(tr[3], OpCode::Ret); + assert_eq!(tr, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Add, OpCode::Ret]); } #[test] @@ -302,18 +287,11 @@ mod tests { let main = &comp.wordlist.0[0]; assert_eq!(main.len(), 1); assert_eq!(main[0], OpCode::If(1, Some(2))); + assert_eq!(main, vec![OpCode::If(1, Some(2))]); let tr = &comp.wordlist.0[1]; - assert_eq!(tr.len(), 4); - assert_eq!(tr[0], OpCode::Num(1)); - assert_eq!(tr[1], OpCode::Num(2)); - assert_eq!(tr[2], OpCode::Add); - assert_eq!(tr[3], OpCode::Ret); + assert_eq!(tr, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Add, OpCode::Ret]); let fl = &comp.wordlist.0[2]; - assert_eq!(fl.len(), 4); - assert_eq!(fl[0], OpCode::Num(1)); - assert_eq!(fl[1], OpCode::Num(2)); - assert_eq!(fl[2], OpCode::Sub); - assert_eq!(fl[3], OpCode::Ret); + assert_eq!(fl, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Sub, OpCode::Ret]); } #[test] @@ -321,6 +299,12 @@ mod tests { let comp = compiler_for(": first + ; : second 3 first ; 1 second\n"); assert_eq!(comp.wordlist.0.len(), 3, "wordlist length"); eprintwordlist(&comp.wordlist); + let main = &comp.wordlist.0[0]; + assert_eq!(main, vec![OpCode::Num(1), OpCode::Call(2)]); + let first = &comp.wordlist.0[1]; + assert_eq!(first, vec![OpCode::Add, OpCode::Ret]); + let second = &comp.wordlist.0[2]; + assert_eq!(second, vec![OpCode::Num(3), OpCode::TCall(1)]); } #[test] @@ -329,18 +313,11 @@ mod tests { eprintwordlist(&comp.wordlist); assert_eq!(comp.wordlist.0.len(), 3); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Call(1)); + assert_eq!(main, vec![OpCode::Call(1)]); let t = &comp.wordlist.0[1]; - assert_eq!(t.len(), 2); - assert_eq!(t[0], OpCode::TIf(2, None)); - assert_eq!(t[1], OpCode::Ret); + assert_eq!(t, vec![OpCode::TIf(2, None), OpCode::Ret]); let tr = &comp.wordlist.0[2]; - assert_eq!(tr.len(), 4); - assert_eq!(tr[0], OpCode::Num(1)); - assert_eq!(tr[1], OpCode::Num(2)); - assert_eq!(tr[2], OpCode::Add); - assert_eq!(tr[3], OpCode::Ret); + assert_eq!(tr, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Add, OpCode::Ret]); } #[test] @@ -349,24 +326,13 @@ mod tests { eprintwordlist(&comp.wordlist); assert_eq!(comp.wordlist.0.len(), 4, "wordlist length"); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Call(1)); + assert_eq!(main, vec![OpCode::Call(1)]); let t = &comp.wordlist.0[1]; - assert_eq!(t.len(), 2); - assert_eq!(t[0], OpCode::TIf(2, Some(3))); - assert_eq!(t[1], OpCode::Ret); + assert_eq!(t, vec![OpCode::TIf(2, Some(3)), OpCode::Ret]); let tr = &comp.wordlist.0[2]; - assert_eq!(tr.len(), 4); - assert_eq!(tr[0], OpCode::Num(1)); - assert_eq!(tr[1], OpCode::Num(2)); - assert_eq!(tr[2], OpCode::Add); - assert_eq!(tr[3], OpCode::Ret); + assert_eq!(tr, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Add, OpCode::Ret]); let fl = &comp.wordlist.0[3]; - assert_eq!(fl.len(), 4); - assert_eq!(fl[0], OpCode::Num(1)); - assert_eq!(fl[1], OpCode::Num(2)); - assert_eq!(fl[2], OpCode::Sub); - assert_eq!(fl[3], OpCode::Ret); + assert_eq!(fl, vec![OpCode::Num(1), OpCode::Num(2), OpCode::Sub, OpCode::Ret]); } #[test] @@ -375,10 +341,8 @@ mod tests { eprintwordlist(&comp.wordlist); assert_eq!(comp.wordlist.0.len(), 2); let main = &comp.wordlist.0[0]; - assert_eq!(main.len(), 1); - assert_eq!(main[0], OpCode::Call(1)); + assert_eq!(main, vec![OpCode::Call(1)]); let foo = &comp.wordlist.0[1]; - assert_eq!(foo.len(), 1); - assert_eq!(foo[0], OpCode::TCall(1)); + assert_eq!(foo, vec![OpCode::TCall(1)]); } } diff --git a/src/forth/vm.rs b/src/forth/vm.rs index b4ce674..fe034a6 100644 --- a/src/forth/vm.rs +++ b/src/forth/vm.rs @@ -2,7 +2,7 @@ use log::debug; use std::ops::Index; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum OpCode { Num(i32), Str(usize, usize), @@ -27,7 +27,7 @@ pub enum OpCode { Ret, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct ByteCode(pub Vec<OpCode>); impl std::ops::Deref for ByteCode { type Target = Vec<OpCode>; @@ -40,6 +40,19 @@ impl ByteCode { pub fn len(&self) -> usize { self.0.len() } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +// really just to make unit testing look nicer. should probably move +// it there. +impl PartialEq<Vec<OpCode>> for &ByteCode { + fn eq(&self, other: &Vec<OpCode>) -> bool { + self.0.len() == other.len() && + std::iter::zip(&self.0, other).all(|(a, b)| a == b) + } } impl Index<usize> for ByteCode { @@ -65,6 +78,12 @@ impl InstructionPointer { } } +impl Default for InstructionPointer { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug)] pub struct DataStack(pub Vec<i32>); @@ -119,7 +138,7 @@ impl VM { 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::Str(start, end) => debug!("got str: {start} to {end}"), OpCode::Add => { let n1 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; let n2 = self.stack.0.pop().ok_or(RuntimeError::StackUnderflow)?; @@ -511,21 +530,17 @@ mod tests { let mut vm = VM::new(wordlist); vm.tick()?; - assert_eq!(vm.stack.0.len(), 0, "call(1) stack len"); + assert_eq!(vm.stack.0, vec![], "call(1) stack"); vm.tick()?; - assert_eq!(vm.stack.0.len(), 1, "push 0 stack len"); - assert_eq!(vm.stack.0[0], 0, "push 0 val"); + assert_eq!(vm.stack.0, vec![0], "push 0 stack"); vm.tick()?; - assert_eq!(vm.stack.0.len(), 0, "tif stack len"); + assert_eq!(vm.stack.0, vec![], "tif stack"); vm.tick()?; - assert_eq!(vm.stack.0.len(), 1, "push 2 stack len"); - assert_eq!(vm.stack.0[0], 2, "push 2 val"); + assert_eq!(vm.stack.0, vec![2], "push 2 stack"); vm.tick()?; - assert_eq!(vm.stack.0.len(), 1, "ret stack len"); - assert_eq!(vm.stack.0[0], 2, "ret val"); + assert_eq!(vm.stack.0, vec![2], "ret stack"); vm.tick()?; - assert_eq!(vm.stack.0.len(), 2, "push -2 stack len"); - assert_eq!(vm.stack.0[1], -2, "push -2 val"); + assert_eq!(vm.stack.0, vec![2, -2], "push -2 stack"); Ok(()) } @@ -1,5 +1,4 @@ use log::{Level, error, info}; -use console_log; use wasm_bindgen::prelude::*; pub mod forth; @@ -25,11 +24,11 @@ impl ExportedByteCode { fn tr_op(op: &forth::vm::OpCode) -> String { use forth::vm::OpCode::*; let s = match op { - If(t, None) => format!("If({}, none)", t), - If(t, Some(f)) => format!("If({}, {})", t, f), - TIf(t, None) => format!("TIf({}, none)", t), - TIf(t, Some(f)) => format!("TIf({}, {})", t, f), - other => format!("{:?}", other), + If(t, None) => format!("If({t}, none)"), + If(t, Some(f)) => format!("If({t}, {f})"), + TIf(t, None) => format!("TIf({t}, none)"), + TIf(t, Some(f)) => format!("TIf({t}, {f})"), + other => format!("{other:?}"), }; s.to_string() } @@ -47,6 +46,10 @@ impl ExportedByteCode { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn at(&self, offset: usize) -> String { self.0[offset].clone() } @@ -86,6 +89,10 @@ impl ExportedWordAnnotations { self.0.len() } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn at(&self, offset: usize) -> ExportedAnnotation { self.0[offset].clone() } @@ -104,15 +111,15 @@ impl ExportedVM { } pub fn compile(&mut self, text: &str) -> bool { - let mut comp = forth::compiler::Compiler::new(&text); + let mut comp = forth::compiler::Compiler::new(text); if let Err(e) = comp.compile() { - error!("couldn't compile program text: {:?}", e); + error!("couldn't compile program text: {e:?}"); return false } self.annos = comp.annotations.iter() .map(|word_anno| -> ExportedWordAnnotations { - word_anno.into_iter().map(|a| a.into()).collect() + word_anno.iter().map(|a| a.into()).collect() }) .collect(); let vm = forth::vm::VM::new(comp.wordlist); @@ -124,7 +131,7 @@ impl ExportedVM { let Some(vm) = &mut self.vm else { return Err("no vm".to_string()) }; - vm.tick().or_else(|err| Err(format!("runtime error: {:?}", err))) + vm.tick().map_err(|err| format!("runtime error: {err:?}")) } pub fn run(&mut self) -> Result<usize, String> { @@ -134,7 +141,7 @@ impl ExportedVM { vm.ip.word = 0; vm.ip.offset = 0; - vm.run().or_else(|err| Err(format!("runtime error: {:?}", err))) + vm.run().map_err(|err| format!("runtime error: {err:?}")) } pub fn stack(&self) -> Vec<i32> { @@ -142,7 +149,7 @@ impl ExportedVM { return vec![] }; - return vm.stack.0.clone() + vm.stack.0.clone() } pub fn wordlist(&self) -> Vec<ExportedByteCode> { |
