WIP ISA Unit tests
Some checks failed
per-push tests / test-frontend (push) Blocked by required conditions
per-push tests / timed-decomposer-parse (push) Blocked by required conditions
per-push tests / test-utility (push) Blocked by required conditions
per-push tests / test-backend (push) Blocked by required conditions
per-push tests / build (push) Has been cancelled

Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2025-07-30 00:01:07 +00:00
parent 609e65a8db
commit 3a06e40e9d
6 changed files with 544 additions and 156 deletions

View file

@ -79,4 +79,6 @@ jobs:
run: cargo test util run: cargo test util
- name: test garbage collection - name: test garbage collection
run: cargo test heap run: cargo test heap
- name: test instruction set implementation
run: cargo test isa_

View file

@ -29,7 +29,7 @@ description = "test if a name is already bound"
name = "push" name = "push"
args = ["operand"] args = ["operand"]
output = "" output = ""
description = "pushes operand onto stack." description = "pushes deep copy of operand onto stack."
[[instructions]] [[instructions]]
name = "pop" name = "pop"
@ -50,7 +50,7 @@ output = ""
description = "delete current stack frame" description = "delete current stack frame"
[[instructions]] [[instructions]]
name = "load" name = "link"
args = ["src", "dest"] args = ["src", "dest"]
output = "" output = ""
description = "shallow copies src into dest" description = "shallow copies src into dest"

View file

@ -21,6 +21,7 @@ use core::ptr::NonNull;
use alloc::rc::Rc; use alloc::rc::Rc;
use alloc::vec::Vec; use alloc::vec::Vec;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::fmt::Debug;
use alloc::string::String; use alloc::string::String;
use organelle::Number; use organelle::Number;
@ -112,6 +113,15 @@ impl<T> Clone for Gc<T> {
} }
} }
impl<T: Debug> Debug for Gc<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let refs = Rc::strong_count(unsafe { self.0.as_ref() });
write!(f, "<refs={refs}>: ")?;
write!(f, "{:?}", (*(unsafe { self.0.as_ref() })).as_ref())?;
Ok(())
}
}
impl<T> Drop for Gc<T> { impl<T> Drop for Gc<T> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
@ -132,7 +142,7 @@ impl<T: Clone> Gc<T> {
} }
} }
#[derive(PartialEq)] #[derive(PartialEq, Debug)]
pub enum Datum { pub enum Datum {
Number(Number), Number(Number),
Bool(bool), Bool(bool),
@ -168,7 +178,7 @@ impl Clone for Datum {
} }
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq, Debug)]
pub struct Cons(pub Option<Gc<Datum>>, pub Option<Gc<Datum>>); pub struct Cons(pub Option<Gc<Datum>>, pub Option<Gc<Datum>>);
impl Cons { impl Cons {

View file

@ -131,10 +131,10 @@ impl<T: Debug> Debug for StackStack<T> {
let mut ss_idx = 1; let mut ss_idx = 1;
let mut ss_cur = &*self.0; let mut ss_cur = &*self.0;
while let Some(inner) = ss_cur { while let Some(inner) = ss_cur {
write!(f, "Frame {ss_idx}:")?; write!(f, "Frame {ss_idx}:\n")?;
let mut s_cur = &*inner.stack.0; let mut s_cur = &*inner.stack.0;
while let Some(node) = s_cur { while let Some(node) = s_cur {
write!(f, " {:#?}", node.data)?; write!(f, " > {:?}\n", node.data)?;
s_cur = &*node.next.0; s_cur = &*node.next.0;
} }
write!(f, "\n")?; write!(f, "\n")?;
@ -278,4 +278,12 @@ mod tests {
assert_eq!(g[1], 1); assert_eq!(g[1], 1);
assert_eq!(g[2], 0); assert_eq!(g[2], 0);
} }
#[test]
fn test_stack_idx_zero() {
let mut g = StackStack::<i8>::new();
g.add_stack();
g.push_current_stack(2);
assert_eq!(g[0], 2);
}
} }

View file

@ -258,23 +258,23 @@ mod tests {
assert_eq!(oe_bytes.unwrap(), vec![instr::TRAP.0, 0xf3]); assert_eq!(oe_bytes.unwrap(), vec![instr::TRAP.0, 0xf3]);
let two_operands = let two_operands =
TryInto::<Instruction>::try_into(&[instr::LOAD.0, 0xf3, 0xf4][..]); TryInto::<Instruction>::try_into(&[instr::LINK.0, 0xf3, 0xf4][..]);
assert!(two_operands.is_ok()); assert!(two_operands.is_ok());
let two_oper = two_operands.unwrap(); 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); assert_eq!(two_oper.1.len(), 2);
let two_bytes = let two_bytes =
TryInto::<Vec<u8>>::try_into(two_oper.clone()); TryInto::<Vec<u8>>::try_into(two_oper.clone());
assert!(two_bytes.is_ok()); 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[0], Operand(Address::Oper1, 0));
assert_eq!(two_oper.1[1], Operand(Address::Oper2, 0)); assert_eq!(two_oper.1[1], Operand(Address::Oper2, 0));
} }
#[test] #[test]
fn test_program_parse() { fn test_program_parse() {
let bytes1 = [instr::LOAD.0, 0xf3, 0xf4]; let bytes1 = [instr::LINK.0, 0xf3, 0xf4];
let out1 = vec![Instruction(instr::LOAD, let out1 = vec![Instruction(instr::LINK,
vec![Operand(Address::Oper1, 0), Operand(Address::Oper2, 0)])]; vec![Operand(Address::Oper1, 0), Operand(Address::Oper2, 0)])];
let res1 = let res1 =
TryInto::<Program>::try_into(&bytes1[..]); TryInto::<Program>::try_into(&bytes1[..]);
@ -282,11 +282,11 @@ mod tests {
assert_eq!(res1.unwrap().0, out1); assert_eq!(res1.unwrap().0, out1);
let bytes2 = [ let bytes2 = [
instr::LOAD.0, 0xf3, 0xf4, instr::LINK.0, 0xf3, 0xf4,
instr::CLEAR.0, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 instr::CLEAR.0, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]; ];
let out2 = vec![ let out2 = vec![
Instruction(instr::LOAD, vec![ Instruction(instr::LINK, vec![
Operand(Address::Oper1, 0), Operand(Address::Oper1, 0),
Operand(Address::Oper2, 0) Operand(Address::Oper2, 0)
]), ]),

View file

@ -62,7 +62,7 @@ pub struct VM {
impl From<Program> for VM { impl From<Program> for VM {
fn from(value: Program) -> Self { fn from(value: Program) -> Self {
VM{ VM{
stack: StackStack::new(), stack: StackStack::<Gc<Datum>>::new(),
symtab: QuickMap::new(), symtab: QuickMap::new(),
prog: value, prog: value,
traps: vec![], traps: vec![],
@ -143,8 +143,6 @@ impl VM {
#[inline(always)] #[inline(always)]
fn execute_instruction(&mut self) { fn execute_instruction(&mut self) {
let instr = &self.prog.0[self.ictr].clone();
macro_rules! e { macro_rules! e {
( $err:expr ) => { ( $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 // get or set according to addressing mode
macro_rules! access { macro_rules! access {
( $oper:expr ) => { ( $oper:expr ) => {
@ -193,7 +196,7 @@ impl VM {
e!("out of bounds jump caught"); 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"); e!("illegal argument to BOUND instruction");
}; };
let tag = unsafe { str::from_utf8_unchecked(tag) }; 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 // stack ops
i::PUSH => self.stack.push_current_stack( 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::POP => _ = self.stack.pop_current_stack(),
i::ENTER => self.stack.add_stack(), i::ENTER => self.stack.add_stack(),
i::EXIT => self.stack.destroy_top_stack(), i::EXIT => self.stack.destroy_top_stack(),
// movement ops // 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::DUPL => access!(&instr.1[1], access!(&instr.1[0]).deep_copy()),
i::CLEAR => access!(&instr.1[0], Datum::None.into()), i::CLEAR => access!(&instr.1[0], Datum::None.into()),
@ -368,7 +371,7 @@ impl VM {
if let Datum::Char(schr) = **access!(&instr.1[0]) { if let Datum::Char(schr) = **access!(&instr.1[0]) {
Datum::Number(Number::Fra(Fraction(schr as isize, 1))).into() Datum::Number(Number::Fra(Fraction(schr as isize, 1))).into()
} else { } 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() Datum::Char(n.0.trunc() as u64 as u8).into()
} else { } 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], { 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"); e!("illegal argument to CONST instruction");
}; };
@ -588,123 +591,14 @@ impl VM {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::array;
use super::*; use super::*;
use crate::instr; use crate::instr as i;
use crate::util::{Program, Instruction, Operand};
const ISA_TESTS: [Option<Vec<(VM, TestResult)>>; instr::TOTAL_INSTRUCTIONS] = [ use core::ops::Deref;
// TRAP use organelle::Float;
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,
];
fn has_stack(state: &VM, idx: usize, target: Gc<Datum>) -> bool { fn has_stack(state: &VM, idx: usize, target: Gc<Datum>) -> bool {
*(state.stack[idx]) == *target state.stack[idx].deref() == target.deref()
} }
fn has_sym(state: &VM, sym: &str, operand: Option<Operand>) -> bool { fn has_sym(state: &VM, sym: &str, operand: Option<Operand>) -> bool {
@ -723,16 +617,28 @@ mod tests {
impl TestResult { impl TestResult {
fn chk_expr(&self, state: &VM) -> bool { 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 { fn chk_err(&self, state: &VM) -> bool {
if let Datum::String(ref msg) = *state.errr { if let Datum::String(ref msg) = *state.errr {
let msg = unsafe { str::from_utf8_unchecked(msg) }; let msg = unsafe { str::from_utf8_unchecked(msg) };
msg == self.errr.unwrap() if msg == self.errr.unwrap() {
} else if let Datum::None = *state.errr { true
self.errr.is_none()
} else { } 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 false
} }
} }
@ -740,6 +646,9 @@ mod tests {
fn chk_stack(&self, state: &VM) -> bool { fn chk_stack(&self, state: &VM) -> bool {
for i in &self.stack { for i in &self.stack {
if !has_stack(&state, i.0, i.1.clone()) { 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 return false
} }
} }
@ -749,33 +658,492 @@ mod tests {
fn chk_syms(&self, state: &VM) -> bool { fn chk_syms(&self, state: &VM) -> bool {
for i in &self.syms { for i in &self.syms {
if !has_sym(&state, i.0, i.1.clone()) { 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 return false
} }
} }
true 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.expr.is_none() || self.chk_expr(&state)) &&
(self.stack.is_empty() || self.chk_stack(&state)) && (self.stack.is_empty() || self.chk_stack(&state)) &&
(self.syms.is_empty() || self.chk_syms(&state)) && (self.syms.is_empty() || self.chk_syms(&state))
(self.errr.is_none() || self.chk_err(&state))
} }
} }
#[test] #[test]
fn run_isa_tests() { fn isa_trap_tests() {
for i in &ISA_TESTS.to_vec() { let mut vm = VM::from(Program(vec![
let Some(test_case) = i else { Instruction(i::TRAP, vec![Operand(Address::Numer, 0)])
assert!(false); // dont let untested instructions happen ])).with_traps(Some(vec![
return Arc::from(|state: &mut VM| {
state.expr = Datum::Bool(true).into()
})
])).to_owned();
let case = TestResult{
expr: Some(Datum::Bool(true).into()),
stack: vec![],
syms: vec![],
errr: None,
}; };
for i in test_case {
let (mut vm, result) = (i.0.clone(), i.1.clone());
vm.run_program(); vm.run_program();
assert!(result.test_passes(vm)); 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::<Gc<Datum>>::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::<Gc<Datum>>::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::<Gc<Datum>>::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::<Gc<Datum>>::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
} }
} }