HyphaeVM Garbage Collection
All checks were successful
per-push tests / build (push) Successful in 36s
per-push tests / test-utility (push) Successful in 36s
per-push tests / test-frontend (push) Successful in 37s
per-push tests / test-backend (push) Successful in 31s
per-push tests / timed-decomposer-parse (push) Successful in 34s
All checks were successful
per-push tests / build (push) Successful in 36s
per-push tests / test-utility (push) Successful in 36s
per-push tests / test-frontend (push) Successful in 37s
per-push tests / test-backend (push) Successful in 31s
per-push tests / timed-decomposer-parse (push) Successful in 34s
This commit removes the dependency upon Mycelium::sexpr::Datum from Hyphae
and instead adds a heap.rs module including three types:
- Gc: This is essentially a Box<Rc<T>> but passed around as a *const Rc<T>.
not only does this allow the wrapped T to be passed around in a format
that fits completely within a single physical register, this also
applies a greedy reference counting garbage collection to each and
every object allocated in the VM.
- Datum: This is a simplified enum for type polymorphism. Similar to the
original Mycelium::sexpr::Datum.
- Cons: This is a very standard Cons cell type.
Additionally, two new instructions are added:
- DUPL: a deep copy instruction
- CONST: an instruction to create number datum from embedded constants
- NTOI: casts a number to its inexact form
- NTOE: casts a number to its exact form
Fixes: #35 and #36
Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
8d2d0ebf0c
commit
cf626b2bfc
7 changed files with 324 additions and 295 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -166,6 +166,7 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
|||
name = "mycelium"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hyphae",
|
||||
"organelle",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,13 @@ description = "delete current stack frame"
|
|||
name = "load"
|
||||
args = ["src", "dest"]
|
||||
output = ""
|
||||
description = "copies src into dest"
|
||||
description = "shallow copies src into dest"
|
||||
|
||||
[[instructions]]
|
||||
name = "dupl"
|
||||
args = ["src", "dest"]
|
||||
output = ""
|
||||
description = "deep copies src into dest"
|
||||
|
||||
[[instructions]]
|
||||
name = "clear"
|
||||
|
|
@ -247,6 +253,12 @@ args = ["src"]
|
|||
output = ""
|
||||
description = "mutates a number datum into its inexact form"
|
||||
|
||||
[[instructions]]
|
||||
name = "const"
|
||||
args = ["dst", "data"]
|
||||
output = ""
|
||||
description = "sets dst location to constant integer data"
|
||||
|
||||
[[instructions]]
|
||||
name = "mkvec"
|
||||
args = []
|
||||
|
|
@ -300,4 +312,3 @@ name = "cdr"
|
|||
args = ["list"]
|
||||
output = "returns last element in cons cell"
|
||||
description = "takes an AST and returns last element in top level cons cell"
|
||||
|
||||
|
|
|
|||
|
|
@ -15,123 +15,170 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use core::fmt::{self, Formatter};
|
||||
use core::ops::Index;
|
||||
use core::ops::{Index, Deref, DerefMut};
|
||||
use core::ptr::NonNull;
|
||||
use core::cell::RefCell;
|
||||
|
||||
use alloc::format;
|
||||
use alloc::rc::Rc;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
|
||||
use organelle::Number;
|
||||
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
/* NOTE
|
||||
* decided not to implement a cache or a singleton heap manager
|
||||
* because I did not want to involve a datatype that would add
|
||||
* unneeded logic to where and how the Rcs get allocated or that
|
||||
* would require relocation if more Rcs were allocated. Any
|
||||
* ADT containing the source data referenced by Gc would add
|
||||
* overhead without value.
|
||||
*
|
||||
* Meanwhile, just using allocated-at-site Rcs provides accurate
|
||||
* reference counting garbage collection. We hack the Box::into_raw
|
||||
* function to pass around heap allocated Rcs.
|
||||
*/
|
||||
|
||||
/* Gc
|
||||
* This is a heap allocated Rc passed around such that it fits into
|
||||
* a physical register. The pointer is to a Box<Rc<T>>, but custom
|
||||
* deref implementation will ensure that deref always points to the
|
||||
* encapsulated T
|
||||
*/
|
||||
#[repr(transparent)]
|
||||
pub struct Gc<T>(NonNull<Rc<T>>);
|
||||
|
||||
impl From<Rc<Datum>> for Gc<Datum> {
|
||||
fn from(src: Rc<Datum>) -> Self {
|
||||
Gc(NonNull::new(Box::into_raw(Box::new(src.clone())))
|
||||
.expect("GC obj from rc nonnull ptr check"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Datum> for Gc<Datum> {
|
||||
fn from(value: Datum) -> Self {
|
||||
Gc(NonNull::new(Box::into_raw(Box::new(Rc::from(value))))
|
||||
.expect("GC obj from datum nonnull ptr check"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Gc<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.deref().eq(other.deref())
|
||||
}
|
||||
|
||||
fn ne(&self, other: &Self) -> bool {
|
||||
self.deref().ne(other.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Gc<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
Rc::<T>::as_ptr(self.0.as_ref())
|
||||
.as_ref()
|
||||
.expect("GC obj deref inconsistent rc ptr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Gc<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe {
|
||||
(Rc::<T>::as_ptr(self.0.as_mut()) as *mut T)
|
||||
.as_mut()
|
||||
.expect("GC obj inconsistent rc ptr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// takes a pointer to target Rc
|
||||
macro_rules! shallow_copy_rc {
|
||||
( $src:expr ) => {
|
||||
unsafe {
|
||||
NonNull::new(Box::into_raw(Box::new((*$src).clone())))
|
||||
.expect("GC obj shallow copy nonnull ptr check")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Clone for Gc<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Gc(shallow_copy_rc!(self.0.as_ptr()))
|
||||
}
|
||||
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
self.0 = shallow_copy_rc!(source.0.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Gc<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
drop(Box::from_raw(self.0.as_ptr() as *mut Box<Rc<T>>))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Gc<T> {
|
||||
#[inline]
|
||||
pub fn deep_copy(&self) -> Gc<T> {
|
||||
Gc(unsafe {
|
||||
NonNull::new(Box::into_raw(Box::new(Rc::from(
|
||||
(*(self.0.as_ptr())).clone()))))
|
||||
.expect("GC obj deep copy nonnull ptr check")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Datum {
|
||||
Number(Number),
|
||||
Bool(bool),
|
||||
List(Rc<Ast>),
|
||||
Cons(Cons),
|
||||
Symbol(String),
|
||||
Char(u8),
|
||||
String(Vec<u8>),
|
||||
Vector(RefCell<Vec<Rc<Datum>>>),
|
||||
Vector(RefCell<Vec<Gc<Datum>>>),
|
||||
ByteVector(RefCell<Vec<u8>>),
|
||||
#[default]
|
||||
None,
|
||||
None
|
||||
}
|
||||
|
||||
fn byte_to_escaped_char(b: u8) -> String {
|
||||
// alarm, backspace, delete
|
||||
match b {
|
||||
_ if b > 31 && b < 127 => String::from(b as char),
|
||||
_ => format!("x{:x}", b),
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Cons(pub Option<Gc<Datum>>, pub Option<Gc<Datum>>);
|
||||
|
||||
impl Cons {
|
||||
pub fn deep_copy(&self) -> Cons {
|
||||
// TODO: recursive deep copy through the whole list
|
||||
Cons(self.0.as_ref().map(|x| x.deep_copy()),
|
||||
self.1.as_ref().map(|x| x.deep_copy()))
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_vec<T: fmt::Display>(ve: &RefCell<Vec<T>>) -> String {
|
||||
let v = ve.borrow();
|
||||
if v.len() == 0 {
|
||||
return String::new()
|
||||
}
|
||||
let mut s = format!("{}", v[0]);
|
||||
let mut i = v.iter();
|
||||
i.next(); // discard
|
||||
i.for_each(|e| {
|
||||
s = format!("{} {}", s, e);
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
impl fmt::Display for Datum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Datum::Number(n) => write!(f, "{}", Into::<String>::into(*n)),
|
||||
Datum::Bool(n) => write!(f, "{}", if *n {"#t"} else {"#f"}),
|
||||
Datum::List(n) => write!(f, "{n}"),
|
||||
Datum::Symbol(n) => write!(f, "{n}"),
|
||||
Datum::Char(n) => write!(f, "#\\{}",
|
||||
byte_to_escaped_char(*n)),
|
||||
Datum::String(n) =>
|
||||
write!(f, "\"{}\"", String::from_utf8_lossy(&*n)),
|
||||
Datum::Vector(n) => write!(f, "#({})", fmt_vec(n)),
|
||||
Datum::ByteVector(n) => write!(f, "#u8({})", fmt_vec(n)),
|
||||
Datum::None => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* WARNING
|
||||
* This is in a sense overloaded.
|
||||
* Instead of using this to print debugging information for the
|
||||
* Rust code, I have instead overloaded it to print the most
|
||||
* maximal expanded valid syntax for this Datum
|
||||
*/
|
||||
impl fmt::Debug for Datum {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Datum::Number(n) => write!(f, "{}", Into::<String>::into(*n)),
|
||||
Datum::Bool(n) => write!(f, "{}", if *n {"#t"} else {"#f"}),
|
||||
Datum::List(n) => write!(f, "{n}"),
|
||||
Datum::Char(n) => write!(f, "{}",
|
||||
byte_to_escaped_char(*n)),
|
||||
Datum::Symbol(n) => write!(f, "{n}"),
|
||||
Datum::String(n) =>
|
||||
write!(f, "\"{}\"", String::from_utf8_lossy(&*n)),
|
||||
Datum::Vector(n) => write!(f, "#({n:?})"),
|
||||
Datum::ByteVector(n) => write!(f, "#u8({n:?})"),
|
||||
Datum::None => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, Clone, PartialEq)]
|
||||
pub struct Ast(pub Rc<Datum>, pub Rc<Datum>);
|
||||
|
||||
impl Ast {
|
||||
pub fn subsl(&self, start: isize, end: isize) -> Ast {
|
||||
pub fn subsl(&self, start: isize, end: isize) -> Cons {
|
||||
if end - start == 1 {
|
||||
return Ast(Rc::from(self[start as usize].clone()), Rc::from(Datum::None))
|
||||
return Cons(Some(self[start as usize].clone()), None)
|
||||
}
|
||||
|
||||
if end == 0 {
|
||||
return Ast(
|
||||
Rc::from((*(self.0)).clone()),
|
||||
Rc::from(Datum::None)
|
||||
return Cons(
|
||||
self.0.clone(),
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
let Datum::List(ref next) = *self.1 else {
|
||||
panic!("index into improper list form")
|
||||
let Some(ref next) = self.1 else {
|
||||
panic!("out of bounds subsl of cons list")
|
||||
};
|
||||
|
||||
let Datum::Cons(ref next) = **next else {
|
||||
panic!("subsl of cons list not in standard form")
|
||||
};
|
||||
|
||||
if start <= 0 {
|
||||
Ast(
|
||||
Rc::from((*(self.0)).clone()),
|
||||
Rc::from(Datum::List(
|
||||
Rc::from(next.subsl(start - 1, end - 1))))
|
||||
)
|
||||
Cons(self.0.clone(),
|
||||
Some(Datum::Cons(next.subsl(start - 1, end - 1))
|
||||
.into()))
|
||||
|
||||
} else {
|
||||
next.subsl(start - 1, end - 1)
|
||||
|
|
@ -139,81 +186,43 @@ impl Ast {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let Datum::List(ref next) = *self.1 else {
|
||||
let Some(_) = self.0 else {
|
||||
return 0
|
||||
};
|
||||
|
||||
let Some(ref next) = self.1 else {
|
||||
return 1
|
||||
};
|
||||
|
||||
let Datum::Cons(ref next) = **next else {
|
||||
// weird list but okay
|
||||
return 2
|
||||
};
|
||||
|
||||
1 + next.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Ast {
|
||||
type Item = Rc<Datum>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Datum::List(n) = &*self.1 {
|
||||
let tmp_pair = n;
|
||||
self.0 = tmp_pair.0.clone();
|
||||
self.1 = tmp_pair.1.clone();
|
||||
return Some(self.0.clone());
|
||||
}
|
||||
|
||||
if let Datum::None = *self.1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let tmp = self.1.clone();
|
||||
self.0 = Rc::from(Datum::None);
|
||||
self.1 = Rc::from(Datum::None);
|
||||
return Some(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Ast {
|
||||
type Output = Datum;
|
||||
impl Index<usize> for Cons {
|
||||
type Output = Gc<Datum>;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
if index == 0 {
|
||||
if let Datum::None = *self.0 {
|
||||
panic!("out of bounds indexing into AST")
|
||||
if let Some(data) = &self.0 {
|
||||
data
|
||||
} else {
|
||||
self.0.as_ref()
|
||||
panic!("out of bounds indexing into cons list")
|
||||
}
|
||||
} else {
|
||||
let Datum::List(ref next) = *self.1 else {
|
||||
panic!("out of bounds indexing into AST")
|
||||
let Some(ref next) = self.1 else {
|
||||
panic!("out of bounds indexing into cons list")
|
||||
};
|
||||
|
||||
next.index(index - 1)
|
||||
let Datum::Cons(ref next) = **next else {
|
||||
panic!("cons list not in standard form")
|
||||
};
|
||||
|
||||
&next[index - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}", self.0)?;
|
||||
let mut cur = self;
|
||||
while let Datum::List(next) = &*cur.1 {
|
||||
cur = &next;
|
||||
write!(f, " {}", cur.0)?;
|
||||
}
|
||||
|
||||
if let Datum::None = &*cur.1 {
|
||||
write!(f, ")")
|
||||
} else {
|
||||
write!(f, " . {})", cur.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}", self.0)?;
|
||||
let mut cur = self;
|
||||
let mut end = 1;
|
||||
while let Datum::List(next) = &*cur.1 {
|
||||
cur = &next;
|
||||
end += 1;
|
||||
write!(f, "({} . ", cur.0)?
|
||||
}
|
||||
write!(f, "{}{}", cur.1, ")".repeat(end))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
252
hyphae/src/vm.rs
252
hyphae/src/vm.rs
|
|
@ -22,12 +22,11 @@ use crate::hmap::QuickMap;
|
|||
use crate::stackstack::StackStack;
|
||||
use crate::instr as i;
|
||||
use crate::util::{Operand, Program, Address};
|
||||
use crate::heap::Datum;
|
||||
use crate::heap::{Gc, Datum};
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::rc::Rc;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::borrow::ToOwned;
|
||||
|
|
@ -39,20 +38,20 @@ const NUM_OPERAND_REGISTERS: usize = 4;
|
|||
|
||||
pub struct VM {
|
||||
// execution environment
|
||||
pub stack: StackStack<Datum>,
|
||||
pub stack: StackStack<Gc<Datum>>,
|
||||
pub symtab: QuickMap<Operand>,
|
||||
pub prog: Program,
|
||||
pub fds: Vec<u64>,
|
||||
pub traps: Vec<Arc<dyn Fn(&mut VM)>>,
|
||||
|
||||
// data registers
|
||||
pub expr: Datum,
|
||||
pub oper: [Datum; NUM_OPERAND_REGISTERS],
|
||||
pub expr: Gc<Datum>,
|
||||
pub oper: [Gc<Datum>; NUM_OPERAND_REGISTERS],
|
||||
|
||||
// control flow registers
|
||||
pub retn: usize,
|
||||
pub ictr: usize,
|
||||
pub errr: Datum,
|
||||
pub errr: Gc<Datum>,
|
||||
|
||||
// state
|
||||
pub running: bool,
|
||||
|
|
@ -86,13 +85,14 @@ impl VM {
|
|||
{
|
||||
self.running = false;
|
||||
self.err_state = true;
|
||||
self.errr = Datum::String($err.as_bytes().to_vec());
|
||||
self.errr = Datum::String($err.as_bytes().to_vec()).into();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! deref {
|
||||
// get or set according to addressing mode
|
||||
macro_rules! access {
|
||||
( $oper:expr ) => {
|
||||
match $oper.0 {
|
||||
Address::Expr => &self.expr,
|
||||
|
|
@ -101,23 +101,19 @@ impl VM {
|
|||
Address::Oper3 => &self.oper[2],
|
||||
Address::Oper4 => &self.oper[3],
|
||||
Address::Stack => &self.stack[$oper.1],
|
||||
Address::Numer => e!("attempt to dereference constant numeric data"),
|
||||
Address::Numer => e!("cannot access constant numeric"),
|
||||
Address::Instr => e!("bad access to instruction data"),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
macro_rules! deref_mut {
|
||||
( $oper:expr ) => {
|
||||
match $oper.0 {
|
||||
Address::Expr => &mut self.expr,
|
||||
Address::Oper1 => &mut self.oper[0],
|
||||
Address::Oper2 => &mut self.oper[1],
|
||||
Address::Oper3 => &mut self.oper[2],
|
||||
Address::Oper4 => &mut self.oper[3],
|
||||
Address::Instr => e!("bad mutable access to instruction data"),
|
||||
// Stack, Numer
|
||||
_ => e!("mutable access to immutable data"),
|
||||
( $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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,13 +134,13 @@ impl VM {
|
|||
|
||||
macro_rules! lr_oper {
|
||||
( $in_type:ident, $oper:tt, $out_type:ident ) => {
|
||||
self.expr = Datum::$out_type(*match deref!(&instr.1[0]){
|
||||
self.expr = Datum::$out_type(match **access!(&instr.1[0]){
|
||||
Datum::$in_type(l) => l,
|
||||
_ => e!("illegal argument to instruction"),
|
||||
} $oper *match deref!(&instr.1[1]){
|
||||
} $oper match **access!(&instr.1[1]){
|
||||
Datum::$in_type(l) => l,
|
||||
_ => e!("illegal argument to instruction"),
|
||||
})
|
||||
}).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,38 +159,40 @@ impl VM {
|
|||
|
||||
// symtable ops
|
||||
i::BIND => {
|
||||
let Datum::String(tag) = deref!(&instr.1[0]) else {
|
||||
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to BIND instruction");
|
||||
};
|
||||
let tag = unsafe { str::from_utf8_unchecked(&tag).to_owned() };
|
||||
let tag = unsafe { str::from_utf8_unchecked(tag).to_owned() };
|
||||
self.symtab.insert(tag, instr.1[1].clone());
|
||||
},
|
||||
|
||||
i::UNBIND => {
|
||||
let Datum::String(tag) = deref!(&instr.1[0]) else {
|
||||
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to UNBIND instruction");
|
||||
};
|
||||
let tag = unsafe { str::from_utf8_unchecked(&tag) };
|
||||
self.symtab.remove(&tag);
|
||||
let tag = unsafe { str::from_utf8_unchecked(tag) };
|
||||
self.symtab.remove(tag);
|
||||
},
|
||||
|
||||
i::BOUND => {
|
||||
let Datum::String(tag) = deref!(&instr.1[0]) else {
|
||||
let Datum::String(ref tag) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to BOUND instruction");
|
||||
};
|
||||
let tag = unsafe { str::from_utf8_unchecked(&tag) };
|
||||
self.symtab.contains_key(&tag);
|
||||
let tag = unsafe { str::from_utf8_unchecked(tag) };
|
||||
self.symtab.contains_key(tag);
|
||||
},
|
||||
|
||||
// stack ops
|
||||
i::PUSH => self.stack.push_current_stack(deref!(&instr.1[0]).clone()),
|
||||
i::PUSH => self.stack.push_current_stack(
|
||||
access!(&instr.1[0]).clone()),
|
||||
i::POP => _ = self.stack.pop_current_stack(),
|
||||
i::ENTER => self.stack.add_stack(),
|
||||
i::EXIT => self.stack.destroy_top_stack(),
|
||||
|
||||
// movement ops
|
||||
i::LOAD => *deref_mut!(&instr.1[1]) = deref!(&instr.1[0]).clone(),
|
||||
i::CLEAR => *deref_mut!(&instr.1[0]) = Datum::None,
|
||||
i::LOAD => 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()),
|
||||
|
||||
// control flow ops
|
||||
i::NOP => (),
|
||||
|
|
@ -202,7 +200,7 @@ impl VM {
|
|||
i::PANIC => {
|
||||
self.running = false;
|
||||
self.err_state = false;
|
||||
self.errr = deref!(&instr.1[0]).clone()
|
||||
self.errr = access!(&instr.1[0]).clone();
|
||||
},
|
||||
|
||||
i::JMP => {
|
||||
|
|
@ -210,24 +208,25 @@ impl VM {
|
|||
},
|
||||
|
||||
i::JMPIF => {
|
||||
if let Datum::Bool(true) = self.expr {
|
||||
if let Datum::Bool(true) = *self.expr {
|
||||
do_jmp!(0);
|
||||
}
|
||||
},
|
||||
|
||||
// boolean ops
|
||||
i::EQ => self.expr = Datum::Bool(*deref!(&instr.1[0]) == *deref!(&instr.1[1])),
|
||||
i::EQ => self.expr =
|
||||
Datum::Bool(*access!(&instr.1[0]) == *access!(&instr.1[1])).into(),
|
||||
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(!{
|
||||
let Datum::Bool(a) = self.expr else {
|
||||
let Datum::Bool(a) = *self.expr else {
|
||||
e!("illegal argument to BOOL_NOT instruction");
|
||||
};
|
||||
a
|
||||
});
|
||||
}).into();
|
||||
},
|
||||
|
||||
i::BOOL_AND => lr_oper!(Bool, &&, Bool),
|
||||
|
|
@ -239,11 +238,11 @@ impl VM {
|
|||
i::XOR => lr_oper!(Char, ^, Char),
|
||||
i::BYTE_NOT => {
|
||||
self.expr = Datum::Char(!{
|
||||
let Datum::Char(a) = self.expr else {
|
||||
let Datum::Char(a) = *self.expr else {
|
||||
e!("illegal argument to BYTE_NOT instruction");
|
||||
};
|
||||
a
|
||||
});
|
||||
}).into();
|
||||
},
|
||||
|
||||
// numeric ops
|
||||
|
|
@ -252,11 +251,11 @@ impl VM {
|
|||
i::MUL => lr_oper!(Number, *, Number),
|
||||
i::FDIV => lr_oper!(Number, /, Number),
|
||||
i::IDIV => {
|
||||
let Datum::Number(l) = deref!(&instr.1[0]) else {
|
||||
let Datum::Number(ref l) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to IDIV instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(r) = deref!(&instr.1[1]) else {
|
||||
let Datum::Number(ref r) = **access!(&instr.1[1]) else {
|
||||
e!("illgal argument to IDIV instruction");
|
||||
};
|
||||
|
||||
|
|
@ -268,61 +267,87 @@ impl VM {
|
|||
e!("integer division on non integer value");
|
||||
};
|
||||
|
||||
self.expr = Datum::Number(Number::Fra(Fraction(l / r, 1)));
|
||||
self.expr = Datum::Number(Number::Fra(Fraction(l / r, 1))).into();
|
||||
},
|
||||
|
||||
i::POW => {
|
||||
let Datum::Number(l) = deref!(&instr.1[0]) else {
|
||||
let Datum::Number(ref l) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to POW instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(r) = deref!(&instr.1[1]) else {
|
||||
let Datum::Number(ref r) = **access!(&instr.1[1]) else {
|
||||
e!("illgal argument to POW instruction");
|
||||
};
|
||||
|
||||
self.expr = Datum::Number((*l).pow(*r));
|
||||
self.expr = Datum::Number(l.clone().pow(r.clone())).into();
|
||||
},
|
||||
|
||||
i::INC => if let Datum::Number(src) = deref_mut!(&instr.1[0]) {
|
||||
*src = *src + Number::Fra(Fraction(1, 1));
|
||||
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 {
|
||||
e!("illegal argument to INC instruction");
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
i::DEC => if let Datum::Number(src) = deref_mut!(&instr.1[0]) {
|
||||
*src = *src - Number::Fra(Fraction(1, 1));
|
||||
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 {
|
||||
e!("illegal argument to INC instruction");
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
// byte/char to and from number conversions
|
||||
i::CTON => {
|
||||
let src = deref_mut!(&instr.1[0]);
|
||||
if let Datum::Char(schr) = src {
|
||||
*src = Datum::Number(Number::Fra(Fraction(*schr as isize, 1)));
|
||||
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()
|
||||
} else {
|
||||
e!("illegal argument to CTON instruction");
|
||||
e!("illegal argument to INC instruction");
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
i::NTOC => {
|
||||
let src = deref_mut!(&instr.1[0]);
|
||||
if let Datum::Number(snum) = src {
|
||||
i::NTOC => access!(&instr.1[0], {
|
||||
if let Datum::Number(snum) = **access!(&instr.1[0]) {
|
||||
let n = snum.make_inexact();
|
||||
if !snum.is_exact() || n.0.fract() != 0.0 || n.0 > u8::MAX.into() || n.0 < 0.0 {
|
||||
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");
|
||||
}
|
||||
*src = Datum::Char(n.0.trunc() as u64 as u8);
|
||||
|
||||
Datum::Char(n.0.trunc() as u64 as u8).into()
|
||||
} else {
|
||||
e!("illegal argument to NTOC instruction");
|
||||
e!("illegal argument to INC instruction");
|
||||
}
|
||||
}),
|
||||
|
||||
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::MKVEC => self.expr = Datum::Vector(RefCell::from(vec![])),
|
||||
i::MKBVEC => self.expr = Datum::ByteVector(RefCell::from(vec![])),
|
||||
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())
|
||||
}
|
||||
},
|
||||
|
||||
i::CONST => access!(&instr.1[0], {
|
||||
let Operand(Address::Numer, num) = instr.1[0] else {
|
||||
e!("illegal argument to CONST instruction");
|
||||
};
|
||||
|
||||
Datum::Number(Number::Fra(Fraction(num as isize, 1))).into()
|
||||
}),
|
||||
|
||||
i::MKVEC => self.expr = Datum::Vector(RefCell::from(vec![])).into(),
|
||||
i::MKBVEC => self.expr = Datum::ByteVector(RefCell::from(vec![])).into(),
|
||||
i::INDEX => {
|
||||
let Datum::Number(idx) = deref!(&instr.1[1]) else {
|
||||
let Datum::Number(ref idx) = **access!(&instr.1[1]) else {
|
||||
e!("illegal argument to INDEX instruction");
|
||||
};
|
||||
let idx = idx.make_inexact();
|
||||
|
|
@ -331,40 +356,43 @@ impl VM {
|
|||
}
|
||||
let idx = idx.0.trunc() as usize;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
match **access!(&instr.1[0]) {
|
||||
Datum::Vector(ref v) => {
|
||||
let a = (*v.borrow()[idx].clone()).clone();
|
||||
self.expr = a;
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
Datum::ByteVector(ref bv) => {
|
||||
let a = Datum::Char(bv.borrow()[idx]);
|
||||
self.expr = a;
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::List(l) => self.expr = l[idx].clone(),
|
||||
Datum::Cons(ref l) => self.expr = l[idx].clone(),
|
||||
_ => e!("illegal argument to INDEX instruction")
|
||||
};
|
||||
},
|
||||
|
||||
i::LENGTH => match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
let a = Datum::Number(Number::Fra(Fraction(v.borrow().len() as isize, 1)));
|
||||
self.expr = a;
|
||||
i::LENGTH => match **access!(&instr.1[0]) {
|
||||
Datum::Vector(ref v) => {
|
||||
let a = Datum::Number(Number::Fra(Fraction(
|
||||
v.borrow().len() as isize, 1)));
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let a = Datum::Number(Number::Fra(Fraction(bv.borrow().len() as isize, 1)));
|
||||
self.expr = a;
|
||||
Datum::ByteVector(ref bv) => {
|
||||
let a = Datum::Number(Number::Fra(Fraction(
|
||||
bv.borrow().len() as isize, 1)));
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::List(l) =>
|
||||
self.expr = Datum::Number(Number::Fra(Fraction(l.len() as isize, 1))),
|
||||
Datum::Cons(ref l) => self.expr =
|
||||
Datum::Number(Number::Fra(Fraction(l.len() as isize, 1)))
|
||||
.into(),
|
||||
_ => e!("illegal argument to LENGTH instruction"),
|
||||
},
|
||||
|
||||
i::SUBSL => {
|
||||
let Datum::Number(st) = deref!(&instr.1[1]) else {
|
||||
let Datum::Number(ref st) = **access!(&instr.1[1]) else {
|
||||
e!("illegal argument to SUBSL instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(ed) = deref!(&instr.1[2]) else {
|
||||
let Datum::Number(ref ed) = **access!(&instr.1[2]) else {
|
||||
e!("illegal argument to SUBSL instruction");
|
||||
};
|
||||
|
||||
|
|
@ -382,24 +410,24 @@ impl VM {
|
|||
let st = st.0.trunc() as usize;
|
||||
let ed = ed.0.trunc() as usize;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
match **access!(&instr.1[0]) {
|
||||
Datum::Vector(ref v) => {
|
||||
let a = Datum::Vector(RefCell::from(v.borrow()[st..ed].to_vec()));
|
||||
self.expr = a;
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
Datum::ByteVector(ref bv) => {
|
||||
let a = Datum::ByteVector(RefCell::from(bv.borrow()[st..ed].to_vec()));
|
||||
self.expr = a;
|
||||
self.expr = a.into();
|
||||
},
|
||||
Datum::List(a) =>
|
||||
self.expr = Datum::List(Rc::new(
|
||||
(**a).subsl(st as isize, ed as isize))),
|
||||
|
||||
Datum::Cons(ref a) => self.expr =
|
||||
Datum::Cons(a.subsl(st as isize, ed as isize)).into(),
|
||||
_ => e!("illegal argument to SUBSL instruction")
|
||||
};
|
||||
}
|
||||
|
||||
i::INSER => {
|
||||
let Datum::Number(idx) = deref!(&instr.1[2]) else {
|
||||
let Datum::Number(ref idx) = **access!(&instr.1[2]) else {
|
||||
e!("illegal argument to INSER instruction");
|
||||
};
|
||||
|
||||
|
|
@ -410,34 +438,40 @@ impl VM {
|
|||
|
||||
let idx = idx.0.trunc() as usize;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
v.borrow_mut().insert(idx, deref!(&instr.1[1]).clone().into());
|
||||
match **access!(&instr.1[0]) {
|
||||
Datum::Vector(ref v) => {
|
||||
v.borrow_mut()
|
||||
.insert(idx, access!(&instr.1[1])
|
||||
.deep_copy());
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let Datum::Char(b) = deref!(&instr.1[1]) else {
|
||||
Datum::ByteVector(ref bv) => {
|
||||
let Datum::Char(b) = **access!(&instr.1[1]) else {
|
||||
e!("INSER instruction can only insert a byte into a bytevector");
|
||||
};
|
||||
bv.borrow_mut().insert(idx, *b);
|
||||
bv.borrow_mut().insert(idx, b);
|
||||
},
|
||||
_ => e!("illegal argument to INSER instruction")
|
||||
}
|
||||
},
|
||||
|
||||
i::CAR => {
|
||||
let Datum::List(arg) = deref!(&instr.1[0]) else {
|
||||
let Datum::Cons(ref arg) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to CAR instruction");
|
||||
};
|
||||
|
||||
self.expr = (*arg.0).clone();
|
||||
self.expr = arg.clone().0
|
||||
.or(Some(Datum::None.into()))
|
||||
.expect("CAR instruction option consistency");
|
||||
},
|
||||
|
||||
i::CDR => {
|
||||
let Datum::List(arg) = deref!(&instr.1[0]) else {
|
||||
let Datum::Cons(ref arg) = **access!(&instr.1[0]) else {
|
||||
e!("illegal argument to CAR instruction");
|
||||
};
|
||||
|
||||
self.expr = (*arg.1).clone();
|
||||
self.expr = arg.clone().1
|
||||
.or(Some(Datum::None.into()))
|
||||
.expect("CDR instruction option consistency");
|
||||
},
|
||||
|
||||
i::CONS => {
|
||||
|
|
@ -447,10 +481,6 @@ impl VM {
|
|||
*/
|
||||
},
|
||||
|
||||
// in order to maintain a language agnostic VM these must be traps
|
||||
//i::PARSE => todo!("implement AST API"),
|
||||
//i::EVAL => todo!("implement AST API"),
|
||||
|
||||
_ => {
|
||||
e!("illegal instruction");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
organelle = { path = "../organelle" }
|
||||
|
||||
hyphae = { path = "../hyphae" }
|
||||
|
|
|
|||
|
|
@ -110,34 +110,6 @@ impl fmt::Debug for Datum {
|
|||
pub struct Ast(pub Rc<Datum>, pub Rc<Datum>);
|
||||
|
||||
impl Ast {
|
||||
pub fn subsl(&self, start: isize, end: isize) -> Ast {
|
||||
if end - start == 1 {
|
||||
return Ast(Rc::from(self[start as usize].clone()), Rc::from(Datum::None))
|
||||
}
|
||||
|
||||
if end == 0 {
|
||||
return Ast(
|
||||
Rc::from((*(self.0)).clone()),
|
||||
Rc::from(Datum::None)
|
||||
)
|
||||
}
|
||||
|
||||
let Datum::List(ref next) = *self.1 else {
|
||||
panic!("index into improper list form")
|
||||
};
|
||||
|
||||
if start <= 0 {
|
||||
Ast(
|
||||
Rc::from((*(self.0)).clone()),
|
||||
Rc::from(Datum::List(
|
||||
Rc::from(next.subsl(start - 1, end - 1))))
|
||||
)
|
||||
|
||||
} else {
|
||||
next.subsl(start - 1, end - 1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let Datum::List(ref next) = *self.1 else {
|
||||
return 1
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@ pub enum Number {
|
|||
Sym(SymbolicNumber)
|
||||
}
|
||||
|
||||
impl Default for Number {
|
||||
fn default() -> Self {
|
||||
Number::Fra(Fraction(0, 1))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SymbolicNumber> for Number {
|
||||
fn from(value: SymbolicNumber) -> Self {
|
||||
Number::Sym(value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue