From 3bc256dab26cc593c554f2c67284028663a5c0b7 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Wed, 30 Jul 2025 00:01:07 +0000 Subject: [PATCH] Early ISA Unit tests This commit provides some but not all unit tests for VM instructions. All tests pass, and this commit includes modifications to logic to allow for that. Signed-off-by: Ava Affine --- .forgejo/workflows/test.yml | 2 + hyphae/instructions.toml | 4 +- hyphae/src/heap.rs | 14 +- hyphae/src/stackstack.rs | 12 +- hyphae/src/util.rs | 14 +- hyphae/src/vm.rs | 654 ++++++++++++++++++++++++++++-------- 6 files changed, 544 insertions(+), 156 deletions(-) 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/instructions.toml b/hyphae/instructions.toml index 66b8099..3f02d65 100644 --- a/hyphae/instructions.toml +++ b/hyphae/instructions.toml @@ -29,7 +29,7 @@ description = "test if a name is already bound" name = "push" args = ["operand"] output = "" -description = "pushes operand onto stack." +description = "pushes deep copy of operand onto stack." [[instructions]] name = "pop" @@ -50,7 +50,7 @@ output = "" description = "delete current stack frame" [[instructions]] -name = "load" +name = "link" args = ["src", "dest"] output = "" description = "shallow copies src into dest" diff --git a/hyphae/src/heap.rs b/hyphae/src/heap.rs index 6d79215..b670f80 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,15 @@ impl Clone for Gc { } } +impl Debug for Gc { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let refs = Rc::strong_count(unsafe { self.0.as_ref() }); + write!(f, ": ")?; + write!(f, "{:?}", (*(unsafe { self.0.as_ref() })).as_ref())?; + Ok(()) + } +} + impl Drop for Gc { fn drop(&mut self) { unsafe { @@ -132,7 +142,7 @@ impl Gc { } } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum Datum { Number(Number), Bool(bool), @@ -168,7 +178,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..8513e9a 100644 --- a/hyphae/src/stackstack.rs +++ b/hyphae/src/stackstack.rs @@ -131,10 +131,10 @@ impl Debug for StackStack { let mut ss_idx = 1; let mut ss_cur = &*self.0; while let Some(inner) = ss_cur { - write!(f, "Frame {ss_idx}:")?; + write!(f, "Frame {ss_idx}:\n")?; let mut s_cur = &*inner.stack.0; while let Some(node) = s_cur { - write!(f, " {:#?}", node.data)?; + write!(f, " > {:?}\n", node.data)?; s_cur = &*node.next.0; } write!(f, "\n")?; @@ -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/util.rs b/hyphae/src/util.rs index de902bb..c7d6957 100644 --- a/hyphae/src/util.rs +++ b/hyphae/src/util.rs @@ -258,23 +258,23 @@ mod tests { assert_eq!(oe_bytes.unwrap(), vec![instr::TRAP.0, 0xf3]); let two_operands = - TryInto::::try_into(&[instr::LOAD.0, 0xf3, 0xf4][..]); + TryInto::::try_into(&[instr::LINK.0, 0xf3, 0xf4][..]); assert!(two_operands.is_ok()); let two_oper = two_operands.unwrap(); - assert_eq!(two_oper.0, instr::LOAD); + assert_eq!(two_oper.0, instr::LINK); assert_eq!(two_oper.1.len(), 2); let two_bytes = TryInto::>::try_into(two_oper.clone()); assert!(two_bytes.is_ok()); - assert_eq!(two_bytes.unwrap(), vec![instr::LOAD.0, 0xf3, 0xf4]); + assert_eq!(two_bytes.unwrap(), vec![instr::LINK.0, 0xf3, 0xf4]); assert_eq!(two_oper.1[0], Operand(Address::Oper1, 0)); assert_eq!(two_oper.1[1], Operand(Address::Oper2, 0)); } #[test] fn test_program_parse() { - let bytes1 = [instr::LOAD.0, 0xf3, 0xf4]; - let out1 = vec![Instruction(instr::LOAD, + let bytes1 = [instr::LINK.0, 0xf3, 0xf4]; + let out1 = vec![Instruction(instr::LINK, vec![Operand(Address::Oper1, 0), Operand(Address::Oper2, 0)])]; let res1 = TryInto::::try_into(&bytes1[..]); @@ -282,11 +282,11 @@ mod tests { assert_eq!(res1.unwrap().0, out1); let bytes2 = [ - instr::LOAD.0, 0xf3, 0xf4, + instr::LINK.0, 0xf3, 0xf4, instr::CLEAR.0, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]; let out2 = vec![ - Instruction(instr::LOAD, vec![ + Instruction(instr::LINK, vec![ Operand(Address::Oper1, 0), Operand(Address::Oper2, 0) ]), diff --git a/hyphae/src/vm.rs b/hyphae/src/vm.rs index a181055..2ca76e5 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 ) => { @@ -193,7 +196,7 @@ impl VM { e!("out of bounds jump caught"); } - self.ictr = target; + self.ictr = target - 1; // will post increment in calling func } } @@ -244,18 +247,18 @@ 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 i::PUSH => self.stack.push_current_stack( - access!(&instr.1[0]).clone()), + access!(&instr.1[0]).deep_copy()), i::POP => _ = self.stack.pop_current_stack(), i::ENTER => self.stack.add_stack(), i::EXIT => self.stack.destroy_top_stack(), // movement ops - i::LOAD => access!(&instr.1[1], access!(&instr.1[0]).clone()), + i::LINK => access!(&instr.1[1], access!(&instr.1[0]).clone()), i::DUPL => access!(&instr.1[1], access!(&instr.1[0]).deep_copy()), i::CLEAR => access!(&instr.1[0], Datum::None.into()), @@ -368,7 +371,7 @@ impl VM { if let Datum::Char(schr) = **access!(&instr.1[0]) { Datum::Number(Number::Fra(Fraction(schr as isize, 1))).into() } else { - e!("illegal argument to INC instruction"); + e!("illegal argument to CTON instruction"); } }), @@ -381,7 +384,7 @@ impl VM { } Datum::Char(n.0.trunc() as u64 as u8).into() } else { - e!("illegal argument to INC instruction"); + e!("illegal argument to NTOC instruction"); } }), @@ -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,14 @@ 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; + use organelle::Float; 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,16 +617,28 @@ mod tests { impl TestResult { fn chk_expr(&self, state: &VM) -> bool { - self.expr.is_none() || *(state.expr) == *(self.expr.clone().unwrap()) + if *(state.expr) == *(self.expr.clone().unwrap()) { + true + } else { + print!("expecting expr: {:?}\n", self.expr); + print!("has expr: {:?}\n", state.expr); + false + } } fn chk_err(&self, state: &VM) -> bool { if let Datum::String(ref msg) = *state.errr { let msg = unsafe { str::from_utf8_unchecked(msg) }; - msg == self.errr.unwrap() - } else if let Datum::None = *state.errr { - self.errr.is_none() + if msg == self.errr.unwrap() { + true + } else { + print!("expected error {:?}, got {:?}\n", self.errr, msg); + false + } + } else if let Datum::None = *state.errr && self.errr.is_none(){ + true } else { + print!("expected error {:?}, got {:?}\n", self.errr, state.errr); false } } @@ -740,6 +646,9 @@ 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 {:?}\n", state.stack[i.0].deref()); + print!("\nfull stack:\n{:#?}\n", state.stack); return false } } @@ -749,33 +658,492 @@ 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.chk_err(&state) && (self.expr.is_none() || self.chk_expr(&state)) && (self.stack.is_empty() || self.chk_stack(&state)) && - (self.syms.is_empty() || self.chk_syms(&state)) && - (self.errr.is_none() || self.chk_err(&state)) + (self.syms.is_empty() || self.chk_syms(&state)) } } #[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::LINK, 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::LINK, 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::LINK, 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::Bool(false).into()), + stack: vec![], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + + let mut vm = VM::from(Program(vec![ + Instruction(i::JMP, vec![Operand(Address::Instr, 300)]), + ])).with_stack({ + let mut i = StackStack::new(); + i.push_current_stack(Datum::Bool(false).into()); + Some(i) + }).to_owned(); + + let case = TestResult{ + expr: None, + stack: vec![], + syms: vec![], + errr: Some("out of bounds jump caught"), + }; + + vm.run_program(); + print!("cur: {}\n", vm.ictr); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_conversions() { + /* program: + * dupl %0, $ex + * cton $ex + * push $ex + * ntoc $ex + * push $ex + * ntoi $ex + * push $ex + * ntoe $ex + * push $ex + */ + + let mut vm = VM::from(Program(vec![ + // load from stack into expr + Instruction(i::DUPL, vec![Operand(Address::Stack, 0), + Operand(Address::Expr, 0)]), + + // create a number and push to stack + Instruction(i::CTON, vec![Operand(Address::Expr, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + + // convert to inexact and push to stack + Instruction(i::NTOI, vec![Operand(Address::Expr, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + + // convert back to exact and push to stack + Instruction(i::NTOE, vec![Operand(Address::Expr, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + + // create a char and push to stack + Instruction(i::NTOC, vec![Operand(Address::Expr, 0)]), + Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]), + ])).with_stack({ + let mut i = StackStack::new(); + i.push_current_stack(Datum::Char(b'a').into()); + Some(i) + }).to_owned(); + + let case = TestResult{ + expr: None, + stack: vec![ + (0, Datum::Char(b'a').into()), + (1, Datum::Number(Number::Fra(Fraction(97, 1))).into()), + (2, Datum::Number(Number::Flt(Float(97.0))).into()), + (3, Datum::Number(Number::Fra(Fraction(97, 1))).into()), + (4, Datum::Char(b'a').into()), + ], + syms: vec![], + errr: None, + }; + + vm.run_program(); + assert!(case.test_passes(&vm)); + } + + #[test] + fn isa_index() { + // TODO INDEX on V, BV, S, L + } + + #[test] + fn isa_length() { + // TODO LENGTH on V, BV, S, L + } + + #[test] + fn isa_subsl() { + // TODO SUBSL on V, BV, S, L + } + + #[test] + fn isa_inser() { + // TODO INSER on V, BV, S, L + } + + #[test] + fn isa_cons_ops() { + // TODO CAR and CDR and CONS + } + + #[test] + fn isa_string_ops() { + // TODO CONCAT and S_APPEND } }