2025-06-26 10:52:54 -07:00
|
|
|
/* Mycelium Scheme
|
|
|
|
|
* Copyright (C) 2025 Ava Affine
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
2025-07-24 19:44:43 +00:00
|
|
|
use organelle::{Fraction, Number, Numeric};
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
use crate::hmap::QuickMap;
|
|
|
|
|
use crate::stackstack::StackStack;
|
|
|
|
|
use crate::instr as i;
|
|
|
|
|
use crate::util::{Operand, Program, Address};
|
2025-07-27 07:18:14 +00:00
|
|
|
use crate::heap::{Gc, Datum, Cons};
|
2025-06-26 10:52:54 -07:00
|
|
|
|
2025-07-27 07:18:14 +00:00
|
|
|
use core::ops::DerefMut;
|
2025-07-29 23:29:34 +00:00
|
|
|
use core::array;
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
use alloc::vec;
|
|
|
|
|
use alloc::vec::Vec;
|
|
|
|
|
use alloc::sync::Arc;
|
|
|
|
|
use alloc::borrow::ToOwned;
|
|
|
|
|
|
|
|
|
|
use num::pow::Pow;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const NUM_OPERAND_REGISTERS: usize = 4;
|
|
|
|
|
|
2025-07-29 23:29:34 +00:00
|
|
|
#[derive(Clone)]
|
2025-06-26 10:52:54 -07:00
|
|
|
pub struct VM {
|
|
|
|
|
// execution environment
|
2025-07-26 01:13:13 +00:00
|
|
|
pub stack: StackStack<Gc<Datum>>,
|
2025-06-26 10:52:54 -07:00
|
|
|
pub symtab: QuickMap<Operand>,
|
|
|
|
|
pub prog: Program,
|
|
|
|
|
pub traps: Vec<Arc<dyn Fn(&mut VM)>>,
|
|
|
|
|
|
|
|
|
|
// data registers
|
2025-07-26 01:13:13 +00:00
|
|
|
pub expr: Gc<Datum>,
|
|
|
|
|
pub oper: [Gc<Datum>; NUM_OPERAND_REGISTERS],
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
// control flow registers
|
|
|
|
|
pub retn: usize,
|
|
|
|
|
pub ictr: usize,
|
2025-07-26 01:13:13 +00:00
|
|
|
pub errr: Gc<Datum>,
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
// state
|
|
|
|
|
pub running: bool,
|
|
|
|
|
pub err_state: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 23:29:34 +00:00
|
|
|
impl From<Program> for VM {
|
|
|
|
|
fn from(value: Program) -> Self {
|
|
|
|
|
VM{
|
2025-07-30 00:01:07 +00:00
|
|
|
stack: StackStack::<Gc<Datum>>::new(),
|
2025-07-29 23:29:34 +00:00
|
|
|
symtab: QuickMap::new(),
|
|
|
|
|
prog: value,
|
|
|
|
|
traps: vec![],
|
|
|
|
|
expr: Datum::None.into(),
|
|
|
|
|
oper: array::from_fn(|_| Datum::None.into()),
|
|
|
|
|
retn: 0,
|
|
|
|
|
ictr: 0,
|
|
|
|
|
errr: Datum::None.into(),
|
|
|
|
|
running: true,
|
|
|
|
|
err_state: false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 10:52:54 -07:00
|
|
|
impl VM {
|
2025-07-29 23:29:34 +00:00
|
|
|
pub fn new_with_opts(
|
|
|
|
|
program: Program,
|
|
|
|
|
traps: Option<Vec<Arc<dyn Fn(&mut VM)>>>,
|
|
|
|
|
stack: Option<StackStack<Gc<Datum>>>,
|
|
|
|
|
syms: Option<QuickMap<Operand>>
|
|
|
|
|
) -> VM {
|
|
|
|
|
Into::<VM>::into(program)
|
|
|
|
|
.with_stack(stack)
|
|
|
|
|
.with_symbols(syms)
|
|
|
|
|
.with_traps(traps)
|
|
|
|
|
.to_owned() // not efficient, but we are not executing
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_stack(
|
|
|
|
|
&mut self,
|
|
|
|
|
maybe_stack: Option<StackStack<Gc<Datum>>>,
|
|
|
|
|
) -> &mut VM {
|
|
|
|
|
if let Some(stack) = maybe_stack {
|
|
|
|
|
self.stack = stack;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_symbols(
|
|
|
|
|
&mut self,
|
|
|
|
|
maybe_symbols: Option<QuickMap<Operand>>,
|
|
|
|
|
) -> &mut VM {
|
|
|
|
|
if let Some(symbols) = maybe_symbols {
|
|
|
|
|
self.symtab = symbols;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_traps(
|
|
|
|
|
&mut self,
|
|
|
|
|
maybe_traps: Option<Vec<Arc<dyn Fn(&mut VM)>>>,
|
|
|
|
|
) -> &mut VM {
|
|
|
|
|
if let Some(traps) = maybe_traps {
|
|
|
|
|
self.traps = traps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-26 10:52:54 -07:00
|
|
|
pub fn run_program(&mut self) {
|
|
|
|
|
if self.prog.0.len() < 1 {
|
|
|
|
|
self.running = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while self.ictr < self.prog.0.len() {
|
|
|
|
|
if self.err_state || !self.running {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.execute_instruction();
|
|
|
|
|
self.ictr += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.running = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn execute_instruction(&mut self) {
|
|
|
|
|
macro_rules! e {
|
|
|
|
|
( $err:expr ) => {
|
|
|
|
|
{
|
|
|
|
|
self.running = false;
|
|
|
|
|
self.err_state = true;
|
2025-07-26 01:13:13 +00:00
|
|
|
self.errr = Datum::String($err.as_bytes().to_vec()).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 00:01:07 +00:00
|
|
|
if self.ictr > self.prog.0.len() {
|
|
|
|
|
e!("attempt to execute out of bounds instruction");
|
|
|
|
|
}
|
|
|
|
|
let instr = &self.prog.0[self.ictr].clone();
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
// get or set according to addressing mode
|
|
|
|
|
macro_rules! access {
|
2025-06-26 10:52:54 -07:00
|
|
|
( $oper:expr ) => {
|
|
|
|
|
match $oper.0 {
|
|
|
|
|
Address::Expr => &self.expr,
|
|
|
|
|
Address::Oper1 => &self.oper[0],
|
|
|
|
|
Address::Oper2 => &self.oper[1],
|
|
|
|
|
Address::Oper3 => &self.oper[2],
|
|
|
|
|
Address::Oper4 => &self.oper[3],
|
|
|
|
|
Address::Stack => &self.stack[$oper.1],
|
2025-07-28 23:02:06 +00:00
|
|
|
Address::Numer => e!("cannot access constant numeric data"),
|
2025-08-09 04:45:56 +00:00
|
|
|
Address::Char => e!("cannot access constant char data"),
|
|
|
|
|
Address::Bool => e!("cannot access constant bool data"),
|
2025-06-26 10:52:54 -07:00
|
|
|
Address::Instr => e!("bad access to instruction data"),
|
|
|
|
|
}
|
2025-07-26 01:13:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
( $data:expr, $target:expr ) => {
|
|
|
|
|
match $data.0 {
|
|
|
|
|
Address::Expr => self.expr = $target,
|
|
|
|
|
Address::Oper1 => self.oper[0] = $target,
|
|
|
|
|
Address::Oper2 => self.oper[1] = $target,
|
|
|
|
|
Address::Oper3 => self.oper[2] = $target,
|
|
|
|
|
Address::Oper4 => self.oper[3] = $target,
|
|
|
|
|
_ => e!("attempted mutation of immutable address"),
|
2025-06-26 10:52:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! do_jmp {
|
|
|
|
|
( $idx:expr ) => {
|
|
|
|
|
let Operand(Address::Instr, target) = instr.1[$idx] else {
|
|
|
|
|
e!("illegal argument to jump");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if target >= self.prog.0.len() {
|
|
|
|
|
e!("out of bounds jump caught");
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 00:01:07 +00:00
|
|
|
self.ictr = target - 1; // will post increment in calling func
|
2025-06-26 10:52:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! lr_oper {
|
|
|
|
|
( $in_type:ident, $oper:tt, $out_type:ident ) => {
|
2025-07-26 01:13:13 +00:00
|
|
|
self.expr = Datum::$out_type(match **access!(&instr.1[0]){
|
2025-06-26 10:52:54 -07:00
|
|
|
Datum::$in_type(l) => l,
|
|
|
|
|
_ => e!("illegal argument to instruction"),
|
2025-07-26 01:13:13 +00:00
|
|
|
} $oper match **access!(&instr.1[1]){
|
2025-06-26 10:52:54 -07:00
|
|
|
Datum::$in_type(l) => l,
|
|
|
|
|
_ => e!("illegal argument to instruction"),
|
2025-07-26 01:13:13 +00:00
|
|
|
}).into()
|
2025-06-26 10:52:54 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match instr.0 {
|
|
|
|
|
i::TRAP => {
|
|
|
|
|
let Operand(Address::Numer, idx) = instr.1[0] else {
|
|
|
|
|
e!("illegal argument to TRAP instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if idx >= self.traps.len() {
|
|
|
|
|
e!("access to out of bounds trap!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.traps[idx].clone()(self)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// symtable ops
|
|
|
|
|
i::BIND => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to BIND instruction");
|
|
|
|
|
};
|
2025-07-26 01:13:13 +00:00
|
|
|
let tag = unsafe { str::from_utf8_unchecked(tag).to_owned() };
|
2025-06-26 10:52:54 -07:00
|
|
|
self.symtab.insert(tag, instr.1[1].clone());
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::UNBIND => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to UNBIND instruction");
|
|
|
|
|
};
|
2025-07-26 01:13:13 +00:00
|
|
|
let tag = unsafe { str::from_utf8_unchecked(tag) };
|
|
|
|
|
self.symtab.remove(tag);
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::BOUND => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to BOUND instruction");
|
|
|
|
|
};
|
2025-07-26 01:13:13 +00:00
|
|
|
let tag = unsafe { str::from_utf8_unchecked(tag) };
|
2025-07-30 00:01:07 +00:00
|
|
|
self.expr = Datum::Bool(self.symtab.contains_key(tag)).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// stack ops
|
2025-07-26 01:13:13 +00:00
|
|
|
i::PUSH => self.stack.push_current_stack(
|
2025-07-30 00:01:07 +00:00
|
|
|
access!(&instr.1[0]).deep_copy()),
|
2025-06-26 10:52:54 -07:00
|
|
|
i::POP => _ = self.stack.pop_current_stack(),
|
|
|
|
|
i::ENTER => self.stack.add_stack(),
|
|
|
|
|
i::EXIT => self.stack.destroy_top_stack(),
|
|
|
|
|
|
|
|
|
|
// movement ops
|
2025-07-30 00:01:07 +00:00
|
|
|
i::LINK => access!(&instr.1[1], access!(&instr.1[0]).clone()),
|
2025-07-26 01:13:13 +00:00
|
|
|
i::DUPL => access!(&instr.1[1], access!(&instr.1[0]).deep_copy()),
|
|
|
|
|
i::CLEAR => access!(&instr.1[0], Datum::None.into()),
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
// control flow ops
|
|
|
|
|
i::NOP => (),
|
|
|
|
|
i::HALT => self.running = false,
|
|
|
|
|
i::PANIC => {
|
|
|
|
|
self.running = false;
|
|
|
|
|
self.err_state = false;
|
2025-07-26 01:13:13 +00:00
|
|
|
self.errr = access!(&instr.1[0]).clone();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::JMP => {
|
|
|
|
|
do_jmp!(0);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::JMPIF => {
|
2025-07-26 01:13:13 +00:00
|
|
|
if let Datum::Bool(true) = *self.expr {
|
2025-06-26 10:52:54 -07:00
|
|
|
do_jmp!(0);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// boolean ops
|
2025-07-26 01:13:13 +00:00
|
|
|
i::EQ => self.expr =
|
|
|
|
|
Datum::Bool(*access!(&instr.1[0]) == *access!(&instr.1[1])).into(),
|
2025-06-26 10:52:54 -07:00
|
|
|
i::LT => lr_oper!(Number, <, Bool),
|
|
|
|
|
i::GT => lr_oper!(Number, >, Bool),
|
|
|
|
|
i::LTE => lr_oper!(Number, <=, Bool),
|
|
|
|
|
i::GTE => lr_oper!(Number, >=, Bool),
|
|
|
|
|
i::BOOL_NOT => {
|
|
|
|
|
self.expr = Datum::Bool(!{
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Bool(a) = *self.expr else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to BOOL_NOT instruction");
|
|
|
|
|
};
|
|
|
|
|
a
|
2025-07-26 01:13:13 +00:00
|
|
|
}).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::BOOL_AND => lr_oper!(Bool, &&, Bool),
|
|
|
|
|
i::BOOL_OR => lr_oper!(Bool, ||, Bool),
|
|
|
|
|
|
|
|
|
|
// char / byte ops
|
|
|
|
|
i::BYTE_AND => lr_oper!(Char, &, Char),
|
|
|
|
|
i::BYTE_OR => lr_oper!(Char, |, Char),
|
|
|
|
|
i::XOR => lr_oper!(Char, ^, Char),
|
|
|
|
|
i::BYTE_NOT => {
|
|
|
|
|
self.expr = Datum::Char(!{
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Char(a) = *self.expr else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to BYTE_NOT instruction");
|
|
|
|
|
};
|
|
|
|
|
a
|
2025-07-26 01:13:13 +00:00
|
|
|
}).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// numeric ops
|
|
|
|
|
i::ADD => lr_oper!(Number, +, Number),
|
|
|
|
|
i::SUB => lr_oper!(Number, -, Number),
|
|
|
|
|
i::MUL => lr_oper!(Number, *, Number),
|
|
|
|
|
i::FDIV => lr_oper!(Number, /, Number),
|
|
|
|
|
i::IDIV => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref l) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to IDIV instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref r) = **access!(&instr.1[1]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illgal argument to IDIV instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Fraction(l, 1) = l.make_exact() else {
|
|
|
|
|
e!("integer division on non integer value");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Fraction(r, 1) = r.make_exact() else {
|
|
|
|
|
e!("integer division on non integer value");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
self.expr = Datum::Number(Number::Fra(Fraction(l / r, 1))).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::POW => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref l) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to POW instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref r) = **access!(&instr.1[1]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illgal argument to POW instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
self.expr = Datum::Number(l.clone().pow(r.clone())).into();
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
i::INC => access!(&instr.1[0], {
|
|
|
|
|
if let Datum::Number(src) = **access!(&instr.1[0]) {
|
|
|
|
|
Datum::Number(src + Number::Fra(Fraction(1, 1))).into()
|
|
|
|
|
} else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to INC instruction");
|
2025-07-26 01:13:13 +00:00
|
|
|
}
|
|
|
|
|
}),
|
2025-06-26 10:52:54 -07:00
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
i::DEC => access!(&instr.1[0], {
|
|
|
|
|
if let Datum::Number(src) = **access!(&instr.1[0]) {
|
|
|
|
|
Datum::Number(src - Number::Fra(Fraction(1, 1))).into()
|
|
|
|
|
} else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to INC instruction");
|
2025-07-26 01:13:13 +00:00
|
|
|
}
|
|
|
|
|
}),
|
2025-06-26 10:52:54 -07:00
|
|
|
|
|
|
|
|
// byte/char to and from number conversions
|
2025-07-26 01:13:13 +00:00
|
|
|
i::CTON => access!(&instr.1[0], {
|
|
|
|
|
if let Datum::Char(schr) = **access!(&instr.1[0]) {
|
|
|
|
|
Datum::Number(Number::Fra(Fraction(schr as isize, 1))).into()
|
2025-06-26 10:52:54 -07:00
|
|
|
} else {
|
2025-07-30 00:01:07 +00:00
|
|
|
e!("illegal argument to CTON instruction");
|
2025-06-26 10:52:54 -07:00
|
|
|
}
|
2025-07-26 01:13:13 +00:00
|
|
|
}),
|
2025-06-26 10:52:54 -07:00
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
i::NTOC => access!(&instr.1[0], {
|
|
|
|
|
if let Datum::Number(snum) = **access!(&instr.1[0]) {
|
2025-06-26 10:52:54 -07:00
|
|
|
let n = snum.make_inexact();
|
2025-07-26 01:13:13 +00:00
|
|
|
if !snum.is_exact() || n.0.fract() != 0.0 ||
|
|
|
|
|
n.0 > u8::MAX.into() || n.0 < 0.0 {
|
|
|
|
|
e!("input to NTOC cannot cleanly convert");
|
|
|
|
|
}
|
|
|
|
|
Datum::Char(n.0.trunc() as u64 as u8).into()
|
2025-06-26 10:52:54 -07:00
|
|
|
} else {
|
2025-07-30 00:01:07 +00:00
|
|
|
e!("illegal argument to NTOC instruction");
|
2025-07-26 01:13:13 +00:00
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
i::NTOI => {
|
|
|
|
|
let src = access!(&instr.1[0]);
|
|
|
|
|
if let Datum::Number(snum) = **src {
|
|
|
|
|
access!(&instr.1[0],
|
|
|
|
|
Datum::Number(snum.make_inexact().into()).into())
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::NTOE => {
|
|
|
|
|
let src = access!(&instr.1[0]);
|
|
|
|
|
if let Datum::Number(snum) = **src {
|
|
|
|
|
access!(&instr.1[0], Datum::Number(snum.make_inexact().into())
|
|
|
|
|
.into())
|
2025-06-26 10:52:54 -07:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
i::CONST => access!(&instr.1[0], {
|
2025-08-09 04:45:56 +00:00
|
|
|
match instr.1[1].0 {
|
|
|
|
|
Address::Numer =>
|
|
|
|
|
Datum::Number(Number::Fra(Fraction(instr.1[1].1 as isize, 1)))
|
|
|
|
|
.into(),
|
|
|
|
|
Address::Bool => Datum::Bool(instr.1[1].1 > 0).into(),
|
|
|
|
|
Address::Char => Datum::Char(instr.1[1].1 as u8).into(),
|
|
|
|
|
_ => e!("illegal argument to CONST instruction"),
|
|
|
|
|
}
|
2025-07-26 01:13:13 +00:00
|
|
|
}),
|
|
|
|
|
|
2025-07-29 18:16:10 +00:00
|
|
|
i::MKVEC => self.expr = Datum::Vector(vec![]).into(),
|
|
|
|
|
|
|
|
|
|
i::MKBVEC => self.expr = Datum::ByteVector(vec![]).into(),
|
|
|
|
|
|
2025-06-26 10:52:54 -07:00
|
|
|
i::INDEX => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref idx) = **access!(&instr.1[1]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to INDEX instruction");
|
|
|
|
|
};
|
|
|
|
|
let idx = idx.make_inexact();
|
|
|
|
|
if !idx.is_exact() || idx.0.fract() != 0.0 {
|
|
|
|
|
e!("illegal argument to INDEX instruction");
|
|
|
|
|
}
|
|
|
|
|
let idx = idx.0.trunc() as usize;
|
|
|
|
|
|
2025-07-29 18:16:10 +00:00
|
|
|
match *access!(&instr.1[0]).clone() {
|
|
|
|
|
Datum::Vector(ref v) =>
|
|
|
|
|
self.expr = (*v[idx].clone()).clone().into(),
|
|
|
|
|
Datum::ByteVector(ref bv) =>
|
|
|
|
|
self.expr = Datum::Char(bv[idx]).into(),
|
|
|
|
|
Datum::String(ref s) =>
|
|
|
|
|
self.expr = Datum::Char(s[idx]).into(),
|
2025-07-26 01:13:13 +00:00
|
|
|
Datum::Cons(ref l) => self.expr = l[idx].clone(),
|
2025-06-26 10:52:54 -07:00
|
|
|
_ => e!("illegal argument to INDEX instruction")
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
i::LENGTH => match **access!(&instr.1[0]) {
|
2025-07-29 18:16:10 +00:00
|
|
|
Datum::Vector(ref v) =>
|
|
|
|
|
self.expr = Datum::Number(Number::Fra(Fraction(
|
|
|
|
|
v.len() as isize, 1))).into(),
|
|
|
|
|
Datum::ByteVector(ref bv) =>
|
|
|
|
|
self.expr = Datum::Number(Number::Fra(Fraction(
|
|
|
|
|
bv.len() as isize, 1))).into(),
|
|
|
|
|
Datum::String(ref s) =>
|
|
|
|
|
self.expr = Datum::Number(Number::Fra(Fraction(
|
|
|
|
|
s.len() as isize, 1))).into(),
|
2025-07-26 01:13:13 +00:00
|
|
|
Datum::Cons(ref l) => self.expr =
|
|
|
|
|
Datum::Number(Number::Fra(Fraction(l.len() as isize, 1)))
|
|
|
|
|
.into(),
|
2025-06-26 10:52:54 -07:00
|
|
|
_ => e!("illegal argument to LENGTH instruction"),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::SUBSL => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref st) = **access!(&instr.1[1]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to SUBSL instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref ed) = **access!(&instr.1[2]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to SUBSL instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !st.is_exact() || !ed.is_exact() {
|
|
|
|
|
e!("illegal argument to SUBSL instruction");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let st = st.make_inexact();
|
|
|
|
|
let ed = ed.make_inexact();
|
|
|
|
|
|
|
|
|
|
if st.0.fract() != 0.0 || ed.0.fract() != 0.0 {
|
|
|
|
|
e!("SUBSL: FP precision error");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let st = st.0.trunc() as usize;
|
|
|
|
|
let ed = ed.0.trunc() as usize;
|
|
|
|
|
|
2025-07-29 18:16:10 +00:00
|
|
|
match access!(&instr.1[0]).clone().deref_mut() {
|
|
|
|
|
Datum::Vector(v) =>
|
|
|
|
|
self.expr = Datum::Vector(v[st..ed].to_vec()).into(),
|
|
|
|
|
Datum::ByteVector(bv) =>
|
|
|
|
|
self.expr = Datum::ByteVector(bv[st..ed].to_vec()).into(),
|
|
|
|
|
Datum::String(s) =>
|
|
|
|
|
self.expr = Datum::String(s[st..ed].to_vec()).into(),
|
|
|
|
|
Datum::Cons(a) => self.expr =
|
2025-07-26 01:13:13 +00:00
|
|
|
Datum::Cons(a.subsl(st as isize, ed as isize)).into(),
|
2025-06-26 10:52:54 -07:00
|
|
|
_ => e!("illegal argument to SUBSL instruction")
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i::INSER => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Number(ref idx) = **access!(&instr.1[2]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to INSER instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let idx = idx.make_inexact();
|
|
|
|
|
if !idx.is_exact() || idx.0.fract() != 0.0 {
|
|
|
|
|
e!("illegal argument to INSER instruction");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let idx = idx.0.trunc() as usize;
|
|
|
|
|
|
2025-07-29 18:16:10 +00:00
|
|
|
match access!(&instr.1[0]).clone().deref_mut() {
|
|
|
|
|
Datum::Vector(v) => {
|
|
|
|
|
v.insert(idx, access!(&instr.1[1]).clone());
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
2025-07-29 18:16:10 +00:00
|
|
|
Datum::ByteVector(bv) => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Char(b) = **access!(&instr.1[1]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("INSER instruction can only insert a byte into a bytevector");
|
|
|
|
|
};
|
2025-07-29 18:16:10 +00:00
|
|
|
bv.insert(idx, b);
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
2025-07-29 18:16:10 +00:00
|
|
|
Datum::String(st) => {
|
|
|
|
|
let Datum::Char(b) = **access!(&instr.1[1]) else {
|
|
|
|
|
e!("INSER instruction can only insert a char into a string");
|
|
|
|
|
};
|
|
|
|
|
st.insert(idx, b);
|
|
|
|
|
}
|
2025-06-26 10:52:54 -07:00
|
|
|
_ => e!("illegal argument to INSER instruction")
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::CAR => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Cons(ref arg) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to CAR instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
self.expr = arg.clone().0
|
|
|
|
|
.or(Some(Datum::None.into()))
|
|
|
|
|
.expect("CAR instruction option consistency");
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::CDR => {
|
2025-07-26 01:13:13 +00:00
|
|
|
let Datum::Cons(ref arg) = **access!(&instr.1[0]) else {
|
2025-06-26 10:52:54 -07:00
|
|
|
e!("illegal argument to CAR instruction");
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 01:13:13 +00:00
|
|
|
self.expr = arg.clone().1
|
|
|
|
|
.or(Some(Datum::None.into()))
|
|
|
|
|
.expect("CDR instruction option consistency");
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::CONS => {
|
2025-07-27 07:18:14 +00:00
|
|
|
let mut l = access!(&instr.1[0]).clone();
|
|
|
|
|
if let Datum::Cons(l) = l.deref_mut() {
|
|
|
|
|
l.append(access!(&instr.1[1]).clone());
|
|
|
|
|
} else {
|
|
|
|
|
access!(&instr.1[0], Datum::Cons(Cons(
|
|
|
|
|
Some(l),
|
|
|
|
|
Some(access!(&instr.1[1]).clone())
|
|
|
|
|
)).into());
|
|
|
|
|
}
|
2025-06-26 10:52:54 -07:00
|
|
|
},
|
|
|
|
|
|
2025-07-29 18:16:10 +00:00
|
|
|
i::CONCAT => {
|
|
|
|
|
let Datum::String(ref left) = **access!(&instr.1[0]) else {
|
|
|
|
|
e!("illegal argument to CONCAT instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Datum::String(ref right) = **access!(&instr.1[1]) else {
|
|
|
|
|
e!("illegal argument to CONCAT instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (left, right) = unsafe { (str::from_utf8_unchecked(left).to_owned(),
|
|
|
|
|
str::from_utf8_unchecked(right)) };
|
|
|
|
|
|
|
|
|
|
self.expr = Datum::String((left + right).as_bytes().to_vec()).into();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
i::S_APPEND => {
|
|
|
|
|
let mut left = access!(&instr.1[0]).clone();
|
|
|
|
|
let Datum::String(left) = left.deref_mut() else {
|
|
|
|
|
e!("illegal argument to S_APPEND instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Datum::Char(right) = **access!(&instr.1[1]) else {
|
|
|
|
|
e!("illegal argument to S_APPEND instruction");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
left.push(right);
|
|
|
|
|
},
|
|
|
|
|
|
2025-06-26 10:52:54 -07:00
|
|
|
_ => {
|
|
|
|
|
e!("illegal instruction");
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 23:29:34 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2025-07-30 00:01:07 +00:00
|
|
|
use crate::instr as i;
|
|
|
|
|
use crate::util::{Program, Instruction, Operand};
|
|
|
|
|
use core::ops::Deref;
|
|
|
|
|
use organelle::Float;
|
2025-07-29 23:29:34 +00:00
|
|
|
|
|
|
|
|
fn has_stack(state: &VM, idx: usize, target: Gc<Datum>) -> bool {
|
2025-07-30 00:01:07 +00:00
|
|
|
state.stack[idx].deref() == target.deref()
|
2025-07-29 23:29:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn has_sym(state: &VM, sym: &str, operand: Option<Operand>) -> bool {
|
|
|
|
|
(operand.is_some() == state.symtab.contains_key(sym)) &&
|
|
|
|
|
(operand.is_none() || state.symtab.get(&sym.to_owned()) ==
|
|
|
|
|
operand.as_ref())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
struct TestResult {
|
|
|
|
|
expr: Option<Gc<Datum>>,
|
|
|
|
|
stack: Vec<(usize, Gc<Datum>)>,
|
|
|
|
|
syms: Vec<(&'static str, Option<Operand>)>,
|
|
|
|
|
errr: Option<&'static str>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TestResult {
|
|
|
|
|
fn chk_expr(&self, state: &VM) -> bool {
|
2025-07-30 00:01:07 +00:00
|
|
|
if *(state.expr) == *(self.expr.clone().unwrap()) {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
print!("expecting expr: {:?}\n", self.expr);
|
|
|
|
|
print!("has expr: {:?}\n", state.expr);
|
|
|
|
|
false
|
|
|
|
|
}
|
2025-07-29 23:29:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn chk_err(&self, state: &VM) -> bool {
|
|
|
|
|
if let Datum::String(ref msg) = *state.errr {
|
|
|
|
|
let msg = unsafe { str::from_utf8_unchecked(msg) };
|
2025-08-09 04:45:56 +00:00
|
|
|
if self.errr.is_some() && msg == self.errr.unwrap() {
|
2025-07-30 00:01:07 +00:00
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
print!("expected error {:?}, got {:?}\n", self.errr, msg);
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else if let Datum::None = *state.errr && self.errr.is_none(){
|
|
|
|
|
true
|
2025-07-29 23:29:34 +00:00
|
|
|
} else {
|
2025-07-30 00:01:07 +00:00
|
|
|
print!("expected error {:?}, got {:?}\n", self.errr, state.errr);
|
2025-07-29 23:29:34 +00:00
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn chk_stack(&self, state: &VM) -> bool {
|
|
|
|
|
for i in &self.stack {
|
|
|
|
|
if !has_stack(&state, i.0, i.1.clone()) {
|
2025-07-30 00:01:07 +00:00
|
|
|
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);
|
2025-07-29 23:29:34 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn chk_syms(&self, state: &VM) -> bool {
|
|
|
|
|
for i in &self.syms {
|
|
|
|
|
if !has_sym(&state, i.0, i.1.clone()) {
|
2025-07-30 00:01:07 +00:00
|
|
|
print!("expected symbol {} == {:?}\n", i.0, i.1.clone());
|
|
|
|
|
print!("instead has {:?}\n", state.symtab.get(&i.0.to_owned()));
|
2025-07-29 23:29:34 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-30 00:01:07 +00:00
|
|
|
fn test_passes(&self, state: &VM) -> bool {
|
|
|
|
|
self.chk_err(&state) &&
|
2025-07-29 23:29:34 +00:00
|
|
|
(self.expr.is_none() || self.chk_expr(&state)) &&
|
|
|
|
|
(self.stack.is_empty() || self.chk_stack(&state)) &&
|
2025-07-30 00:01:07 +00:00
|
|
|
(self.syms.is_empty() || self.chk_syms(&state))
|
2025-07-29 23:29:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-07-30 00:01:07 +00:00
|
|
|
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,
|
|
|
|
|
};
|
2025-07-29 23:29:34 +00:00
|
|
|
|
2025-07-30 00:01:07 +00:00
|
|
|
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::<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));
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-09 04:45:56 +00:00
|
|
|
#[test]
|
|
|
|
|
fn isa_consts() {
|
|
|
|
|
let mut vm = VM::from(Program(vec![
|
|
|
|
|
Instruction(i::CONST, vec![Operand(Address::Expr, 0),
|
|
|
|
|
Operand(Address::Numer, 1)]),
|
|
|
|
|
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
|
|
|
|
|
Instruction(i::CONST, vec![Operand(Address::Expr, 0),
|
|
|
|
|
Operand(Address::Char, 'a' as u8 as usize)]),
|
|
|
|
|
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
|
|
|
|
|
Instruction(i::CONST, vec![Operand(Address::Expr, 0),
|
|
|
|
|
Operand(Address::Bool, 1)]),
|
|
|
|
|
Instruction(i::PUSH, vec![Operand(Address::Expr, 0)]),
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
let case = TestResult{
|
|
|
|
|
expr: None,
|
|
|
|
|
stack: vec![
|
|
|
|
|
(0, Datum::Bool(true).into()),
|
|
|
|
|
(1, Datum::Char('a' as u8).into()),
|
|
|
|
|
(2, Datum::Number(Number::Fra(Fraction(1, 1))).into()),
|
|
|
|
|
],
|
|
|
|
|
syms: vec![],
|
|
|
|
|
errr: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vm.run_program();
|
|
|
|
|
assert!(case.test_passes(&vm));
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 21:26:23 +00:00
|
|
|
/*
|
2025-07-30 00:01:07 +00:00
|
|
|
#[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
|
2025-07-29 23:29:34 +00:00
|
|
|
}
|
2025-08-07 21:26:23 +00:00
|
|
|
*/
|
2025-07-29 23:29:34 +00:00
|
|
|
}
|