Early 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

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 <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2025-07-30 00:01:07 +00:00
parent 609e65a8db
commit 3bc256dab2
6 changed files with 544 additions and 156 deletions

View file

@ -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_

View file

@ -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"

View file

@ -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<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> {
fn drop(&mut self) {
unsafe {
@ -132,7 +142,7 @@ impl<T: Clone> Gc<T> {
}
}
#[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<Gc<Datum>>, pub Option<Gc<Datum>>);
impl Cons {

View file

@ -131,10 +131,10 @@ impl<T: Debug> Debug for StackStack<T> {
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::<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]);
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());
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::<Vec<u8>>::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::<Program>::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)
]),

View file

@ -62,7 +62,7 @@ pub struct VM {
impl From<Program> for VM {
fn from(value: Program) -> Self {
VM{
stack: StackStack::new(),
stack: StackStack::<Gc<Datum>>::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<Vec<(VM, TestResult)>>; 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<Datum>) -> bool {
*(state.stack[idx]) == *target
state.stack[idx].deref() == target.deref()
}
fn has_sym(state: &VM, sym: &str, operand: Option<Operand>) -> 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();
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();
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
}
}