diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 0ce7564..735fab5 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -79,4 +79,6 @@ jobs: run: cargo test util - name: test garbage collection run: cargo test heap + - name: test instruction set implementation + run: cargo test isa_ diff --git a/hyphae/src/heap.rs b/hyphae/src/heap.rs index 6d79215..75ce680 100644 --- a/hyphae/src/heap.rs +++ b/hyphae/src/heap.rs @@ -21,6 +21,7 @@ use core::ptr::NonNull; use alloc::rc::Rc; use alloc::vec::Vec; use alloc::boxed::Box; +use alloc::fmt::Debug; use alloc::string::String; use organelle::Number; @@ -112,6 +113,18 @@ impl Clone for Gc { } } +impl Debug for Gc { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("GarbageCollected") + .field("refs", &unsafe { + Rc::strong_count(self.0.as_ref()) + }) + .field("inner", + (*(unsafe { self.0.as_ref() })).as_ref()) + .finish() + } +} + impl Drop for Gc { fn drop(&mut self) { unsafe { @@ -132,7 +145,7 @@ impl Gc { } } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum Datum { Number(Number), Bool(bool), @@ -168,7 +181,7 @@ impl Clone for Datum { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub struct Cons(pub Option>, pub Option>); impl Cons { diff --git a/hyphae/src/stackstack.rs b/hyphae/src/stackstack.rs index 6a403fb..1a7adbf 100644 --- a/hyphae/src/stackstack.rs +++ b/hyphae/src/stackstack.rs @@ -278,4 +278,12 @@ mod tests { assert_eq!(g[1], 1); assert_eq!(g[2], 0); } + + #[test] + fn test_stack_idx_zero() { + let mut g = StackStack::::new(); + g.add_stack(); + g.push_current_stack(2); + assert_eq!(g[0], 2); + } } diff --git a/hyphae/src/vm.rs b/hyphae/src/vm.rs index a181055..378ab23 100644 --- a/hyphae/src/vm.rs +++ b/hyphae/src/vm.rs @@ -62,7 +62,7 @@ pub struct VM { impl From for VM { fn from(value: Program) -> Self { VM{ - stack: StackStack::new(), + stack: StackStack::>::new(), symtab: QuickMap::new(), prog: value, traps: vec![], @@ -143,8 +143,6 @@ impl VM { #[inline(always)] fn execute_instruction(&mut self) { - let instr = &self.prog.0[self.ictr].clone(); - macro_rules! e { ( $err:expr ) => { { @@ -156,6 +154,11 @@ impl VM { } } + if self.ictr > self.prog.0.len() { + e!("attempt to execute out of bounds instruction"); + } + let instr = &self.prog.0[self.ictr].clone(); + // get or set according to addressing mode macro_rules! access { ( $oper:expr ) => { @@ -244,7 +247,7 @@ impl VM { e!("illegal argument to BOUND instruction"); }; let tag = unsafe { str::from_utf8_unchecked(tag) }; - self.symtab.contains_key(tag); + self.expr = Datum::Bool(self.symtab.contains_key(tag)).into(); }, // stack ops @@ -402,7 +405,7 @@ impl VM { }, i::CONST => access!(&instr.1[0], { - let Operand(Address::Numer, num) = instr.1[0] else { + let Operand(Address::Numer, num) = instr.1[1] else { e!("illegal argument to CONST instruction"); }; @@ -588,123 +591,13 @@ impl VM { #[cfg(test)] mod tests { - use core::array; use super::*; - use crate::instr; - - const ISA_TESTS: [Option>; instr::TOTAL_INSTRUCTIONS] = [ - // TRAP - None, - // BIND - None, - // UNBIND - None, - // BOUND - None, - // PUSH - None, - // POP - None, - // ENTER - None, - // EXIT - None, - // LOAD - None, - // DUPL - None, - // CLEAR - None, - // NOP - None, - // HALT - None, - // PANIC - None, - // JMP - None, - // JMPIF - None, - // EQ - None, - // LT - None, - // GT - None, - // LTE - None, - // GTE - None, - // BOOL_NOT - None, - // BOOL_AND - None, - // BOOL_OR - None, - // BYTE_AND - None, - // BYTE_OR - None, - // XOR - None, - // BYTE_NOT - None, - // ADD - None, - // SUB - None, - // MUL - None, - // FDIV - None, - // IDIV - None, - // POW - None, - // MODULO - None, - // REM - None, - // INC - None, - // DEC - None, - // CTON - None, - // NTOC - None, - // NTOI - None, - // NTOE - None, - // CONST - None, - // MKVEC - None, - // MKBVEC - None, - // INDEX - None, - // LENGTH - None, - // SUBSL - None, - // INSER - None, - // CONS - None, - // CAR - None, - // CDR - None, - // CONCAT - None, - // S_APPEND - None, - ]; + use crate::instr as i; + use crate::util::{Program, Instruction, Operand}; + use core::ops::Deref; fn has_stack(state: &VM, idx: usize, target: Gc) -> bool { - *(state.stack[idx]) == *target + state.stack[idx].deref() == target.deref() } fn has_sym(state: &VM, sym: &str, operand: Option) -> bool { @@ -723,6 +616,8 @@ mod tests { impl TestResult { fn chk_expr(&self, state: &VM) -> bool { + print!("expecting expr: {:?}\n", self.expr); + print!("has expr: {:?}\n", state.expr.deref()); self.expr.is_none() || *(state.expr) == *(self.expr.clone().unwrap()) } @@ -740,6 +635,8 @@ mod tests { fn chk_stack(&self, state: &VM) -> bool { for i in &self.stack { if !has_stack(&state, i.0, i.1.clone()) { + print!("expected stack to have {:?} at {}\n", *i.1, i.0); + print!("instead has {:?}", state.stack[i.0].deref()); return false } } @@ -749,13 +646,15 @@ mod tests { fn chk_syms(&self, state: &VM) -> bool { for i in &self.syms { if !has_sym(&state, i.0, i.1.clone()) { + print!("expected symbol {} == {:?}\n", i.0, i.1.clone()); + print!("instead has {:?}\n", state.symtab.get(&i.0.to_owned())); return false } } true } - fn test_passes(&self, state: VM) -> bool { + fn test_passes(&self, state: &VM) -> bool { (self.expr.is_none() || self.chk_expr(&state)) && (self.stack.is_empty() || self.chk_stack(&state)) && (self.syms.is_empty() || self.chk_syms(&state)) && @@ -764,18 +663,377 @@ mod tests { } #[test] - fn run_isa_tests() { - for i in &ISA_TESTS.to_vec() { - let Some(test_case) = i else { - assert!(false); // dont let untested instructions happen - return - }; + fn isa_trap_tests() { + let mut vm = VM::from(Program(vec![ + Instruction(i::TRAP, vec![Operand(Address::Numer, 0)]) + ])).with_traps(Some(vec![ + Arc::from(|state: &mut VM| { + state.expr = Datum::Bool(true).into() + }) + ])).to_owned(); - for i in test_case { - let (mut vm, result) = (i.0.clone(), i.1.clone()); - vm.run_program(); - assert!(result.test_passes(vm)); - } - } + let case = TestResult{ + expr: Some(Datum::Bool(true).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); } + + #[test] + fn isa_symtable_tests() { + let mut vm = VM::from(Program(vec![ + Instruction(i::BIND, vec![Operand(Address::Stack, 1), + Operand(Address::Stack, 0)]), + ])).with_stack(Some({ + let mut i = StackStack::>::new(); + i.push_current_stack(Datum::String("test".into()).into()); + i.push_current_stack(Datum::String("value".into()).into()); + i + })).to_owned(); + + let case = TestResult{ + expr: None, + stack: vec![ + (1, Datum::String("test".into()).into()), + (0, Datum::String("value".into()).into()), + ], + syms: vec![("test", Some(Operand(Address::Stack, 0)))], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::BIND, vec![Operand(Address::Stack, 1), + Operand(Address::Stack, 0)]), + Instruction(i::BOUND, vec![Operand(Address::Stack, 1)]) + ])).with_stack(Some({ + let mut i = StackStack::>::new(); + i.push_current_stack(Datum::String("test".into()).into()); + i.push_current_stack(Datum::String("value".into()).into()); + i + })).to_owned(); + + let case = TestResult{ + expr: Some(Datum::Bool(true).into()), + stack: vec![ + (1, Datum::String("test".into()).into()), + (0, Datum::String("value".into()).into()), + ], + syms: vec![("test", Some(Operand(Address::Stack, 0)))], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::BIND, vec![Operand(Address::Stack, 1), + Operand(Address::Stack, 0)]), + Instruction(i::UNBIND, vec![Operand(Address::Stack, 1)]) + ])).with_stack(Some({ + let mut i = StackStack::>::new(); + i.push_current_stack(Datum::String("test".into()).into()); + i.push_current_stack(Datum::String("value".into()).into()); + i + })).to_owned(); + + let case = TestResult{ + expr: None, + stack: vec![ + (1, Datum::String("test".into()).into()), + (0, Datum::String("value".into()).into()), + ], + syms: vec![("test", None)], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_stack_tests() { + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]) + ])); + + let case = TestResult{ + expr: None, + stack: vec![ + (0, Datum::Number(Number::Fra(Fraction(4, 1))).into()) + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 5)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::POP, vec![]) + ])); + + let case = TestResult{ + expr: None, + stack: vec![ + (0, Datum::Number(Number::Fra(Fraction(4, 1))).into()) + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::ENTER, vec![]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 5)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + ])); + + let case = TestResult{ + expr: None, + stack: vec![ + (1, Datum::Number(Number::Fra(Fraction(4, 1))).into()), + (0, Datum::Number(Number::Fra(Fraction(5, 1))).into()) + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::ENTER, vec![]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 5)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 5)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + Instruction(i::EXIT, vec![]), + ])); + + let case = TestResult{ + expr: None, + stack: vec![ + (0, Datum::Number(Number::Fra(Fraction(4, 1))).into()), + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_load_dupl_clear() { + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::LOAD, vec![Operand(Address::Expr, 0), + Operand(Address::Oper1, 0)]), + Instruction(i::DUPL, vec![Operand(Address::Expr, 0), + Operand(Address::Oper2, 0)]), + Instruction(i::INC, vec![Operand(Address::Oper2, 0)]), + Instruction(i::CLEAR, vec![Operand(Address::Expr, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Oper1, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Oper2, 0)]), + ])); + + let case = TestResult{ + expr: Some(Datum::None.into()), + stack: vec![ + (1, Datum::Number(Number::Fra(Fraction(4, 1))).into()), + (0, Datum::Number(Number::Fra(Fraction(5, 1))).into()) + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_nop_halt_panic() { + let mut vm = VM::from(Program(vec![ + Instruction(i::NOP, vec![]) + ])); + + let case = TestResult{ + expr: None, + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::HALT, vec![]), + Instruction(i::PUSH, vec![Operand(Address::Numer, 1)]) + ])); + + let case = TestResult{ + expr: None, + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::PANIC, vec![Operand(Address::Stack, 0)]) + ])).with_stack({ + let mut i = StackStack::>::new(); + i.push_current_stack(Datum::String("testerr".into()).into()); + Some(i) + }).to_owned(); + + let case = TestResult{ + expr: None, + stack: vec![], + syms: vec![], + errr: Some("testerr"), + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_inc_dec() { + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::INC, vec![Operand(Address::Expr, 0)]), + ])); + + let case = TestResult{ + expr: Some(Datum::Number(Number::Fra(Fraction(5, 1))).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 4)]), + Instruction(i::DEC, vec![Operand(Address::Expr, 0)]), + ])); + + let case = TestResult{ + expr: Some(Datum::Number(Number::Fra(Fraction(3, 1))).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_jmp_jmpif() { + let mut vm = VM::from(Program(vec![ + Instruction(i::JMP, vec![Operand(Address::Instr, 2)]), + Instruction(i::HALT, vec![]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 3)]), + ])); + + let case = TestResult{ + expr: Some(Datum::Number(Number::Fra(Fraction(3, 1))).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::LOAD, vec![Operand(Address::Stack, 0), + Operand(Address::Expr, 0)]), + Instruction(i::JMPIF, vec![Operand(Address::Instr, 3)]), + Instruction(i::HALT, vec![]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 3)]), + ])).with_stack({ + let mut i = StackStack::new(); + i.push_current_stack(Datum::Bool(true).into()); + Some(i) + }).to_owned(); + + let case = TestResult{ + expr: Some(Datum::Number(Number::Fra(Fraction(3, 1))).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::LOAD, vec![Operand(Address::Stack, 0), + Operand(Address::Expr, 0)]), + Instruction(i::JMPIF, vec![Operand(Address::Instr, 3)]), + Instruction(i::HALT, vec![]), + Instruction(i::CONST, vec![Operand(Address::Expr, 0), + Operand(Address::Numer, 3)]), + ])).with_stack({ + let mut i = StackStack::new(); + i.push_current_stack(Datum::Bool(false).into()); + Some(i) + }).to_owned(); + + let case = TestResult{ + expr: Some(Datum::None.into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + // TODO NTOI, NTOE, NTOC, CTON + // TODO INDEX on V, BV, S, L + // TODO LENGTH on V, BV, S, L + // TODO SUBSL on V, BV, S, L + // TODO INSER on V, BV, S, L + // TODO CAR and CDR and CONS + // TODO CONCAT and S_APPEND }