From 3a06e40e9d8482bcba98fd576d183390bbc1c6d1 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Wed, 30 Jul 2025 00:01:07 +0000 Subject: [PATCH] WIP ISA Unit tests 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 } }