WIP HyphaeVM
This commit is a WORK IN PROGRESS for the base implementation of the HyphaeVM. This will be squashed into a larger commit eventually when the work of implementing the HyphaeVM is finished. Do note the in progress number package implementation in snippets Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
3a0a141738
commit
0476160ae3
17 changed files with 2065 additions and 17 deletions
211
hyphae/src/hmap.rs
Executable file
211
hyphae/src/hmap.rs
Executable file
|
|
@ -0,0 +1,211 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
use alloc::slice;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::vec;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
|
||||
/* Use a prime number so that the modulus operation
|
||||
* provides better avalanche effect
|
||||
*/
|
||||
const INDEXED_BUCKETS: u8 = 199;
|
||||
|
||||
/* This only has to work to make quasi unique indexes from
|
||||
* variable names. Any given program will not have so many
|
||||
* symbols that this becomes a bottleneck in runtime.
|
||||
*
|
||||
* Priorities:
|
||||
* - SPEED in embedded code
|
||||
* - avalanche effect
|
||||
*
|
||||
* Not a priority: minimal collisions
|
||||
*
|
||||
* Just to make sure this is not misused I keep it private.
|
||||
* And yes, I am sure a B-Tree would be better.
|
||||
*
|
||||
* TODO: Make sure that the obvious timing attacks
|
||||
* dont create risk for scheme crypto libraries...
|
||||
* or more likely rip and replace with a better nostd hashmap
|
||||
*/
|
||||
#[inline]
|
||||
fn string_hash(input: &str) -> u8 {
|
||||
input
|
||||
.chars()
|
||||
// each letter and number get a digit
|
||||
.map(|c| c.to_digit(36)
|
||||
// all else is 0
|
||||
.or_else(|| Some(0))
|
||||
.unwrap())
|
||||
// modulo reduction
|
||||
.reduce(|acc, i| (acc + i) % INDEXED_BUCKETS as u32)
|
||||
// TODO: some analysis on which cases end up here
|
||||
.or_else(|| Some(0))
|
||||
.unwrap() as u8
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bucket<T: Clone>(Vec<(String, T)>);
|
||||
#[derive(Clone)]
|
||||
pub struct QuickMap<T: Clone>(Box<[Bucket<T>; INDEXED_BUCKETS as usize]>);
|
||||
|
||||
impl<'a, T: Clone> QuickMap<T> {
|
||||
const ARRAY_REPEAT_VALUE: Bucket<T> = Bucket(vec![]);
|
||||
|
||||
pub fn new() -> QuickMap<T> {
|
||||
QuickMap(Box::new([QuickMap::ARRAY_REPEAT_VALUE; INDEXED_BUCKETS as usize]))
|
||||
}
|
||||
|
||||
pub fn get(&self, arg: &String) -> Option<&T> {
|
||||
let idx = string_hash(&arg);
|
||||
for kv in self.0[idx as usize].0.iter() {
|
||||
if &kv.0 == arg {
|
||||
return Some(&kv.1);
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, arg: &str) -> Option<T> {
|
||||
let idx = string_hash(&arg);
|
||||
let len = self.0[idx as usize].0.len();
|
||||
for i in 0..len {
|
||||
if &self
|
||||
.0[idx as usize]
|
||||
.0[i as usize]
|
||||
.0 == arg {
|
||||
return Some(self.0[idx as usize].0.swap_remove(i).1);
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, arg: &str) -> bool {
|
||||
let idx = string_hash(arg);
|
||||
for kv in self.0[idx as usize].0.iter() {
|
||||
if &kv.0 == arg {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, k: String, v: T) -> Option<T> {
|
||||
let idx = string_hash(&k);
|
||||
for kv in self.0[idx as usize].0.iter_mut() {
|
||||
if kv.0 == k {
|
||||
let tmp = kv.1.clone();
|
||||
kv.1 = v;
|
||||
return Some(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
self.0[idx as usize].0.push((k, v));
|
||||
return None
|
||||
}
|
||||
|
||||
pub fn iter(&'a self) -> QuickMapIter<'a, T> {
|
||||
QuickMapIter::<'a, T>{
|
||||
buckets: &self.0,
|
||||
bucket_cursor: 0,
|
||||
vec_iter: self.0[0].0.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct QuickMapIter<'a, T: Clone> {
|
||||
buckets: &'a [Bucket<T>; INDEXED_BUCKETS as usize],
|
||||
bucket_cursor: usize,
|
||||
vec_iter: slice::Iter<'a, (String, T)>,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone> Iterator for QuickMapIter<'a, T> {
|
||||
type Item = &'a (String, T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.vec_iter
|
||||
.next()
|
||||
.or_else(|| {
|
||||
self.bucket_cursor += 1;
|
||||
if self.bucket_cursor == INDEXED_BUCKETS as usize{
|
||||
None
|
||||
} else {
|
||||
self.vec_iter = self.buckets[self.bucket_cursor].0.iter();
|
||||
self.next()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_fetch_and_remove_simple() {
|
||||
let mut q = QuickMap::<u8>::new();
|
||||
let key = String::from("test");
|
||||
q.insert(String::from("test"), 1);
|
||||
assert_eq!(*q.get(&key).unwrap(), 1);
|
||||
assert!(q.contains_key(&key));
|
||||
assert_eq!(
|
||||
q.remove(&key),
|
||||
Some(1),
|
||||
);
|
||||
assert_eq!(q.contains_key(&key), false);
|
||||
assert_eq!(q.get(&key), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_test() {
|
||||
let mut q = QuickMap::<u8>::new();
|
||||
let k1 = String::from("test1");
|
||||
let k2 = String::from("test1@"); // will be in same bucket
|
||||
let k3 = String::from("test2");
|
||||
let k4 = String::from("test2--"); // will be in same bucket
|
||||
q.insert(k1.clone(), 1);
|
||||
q.insert(k2.clone(), 2);
|
||||
q.insert(k3.clone(), 3);
|
||||
q.insert(k4.clone(), 4);
|
||||
|
||||
// test k1 and k2 in same bucket but that other keys are not
|
||||
assert_eq!(q.0[string_hash(&k1) as usize].0.len(), 2);
|
||||
// test k3 and k4 in same bucket but that other keys are not
|
||||
assert_eq!(q.0[string_hash(&k3) as usize].0.len(), 2);
|
||||
|
||||
let mut i = q.iter();
|
||||
let entry1 = i.next().unwrap();
|
||||
let entry2 = i.next().unwrap();
|
||||
let entry3 = i.next().unwrap();
|
||||
let entry4 = i.next().unwrap();
|
||||
|
||||
assert_eq!(i.next(), None);
|
||||
assert_eq!(entry1.0, k1);
|
||||
assert_eq!(entry1.1, 1);
|
||||
assert_eq!(entry2.0, k2);
|
||||
assert_eq!(entry2.1, 2);
|
||||
assert_eq!(entry3.0, k3);
|
||||
assert_eq!(entry3.1, 3);
|
||||
assert_eq!(entry4.0, k4);
|
||||
assert_eq!(entry4.1, 4);
|
||||
}
|
||||
}
|
||||
19
hyphae/src/instr.rs
Normal file
19
hyphae/src/instr.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/hyphae_instr.rs"));
|
||||
|
||||
26
hyphae/src/lib.rs
Normal file
26
hyphae/src/lib.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
|
||||
pub mod hmap;
|
||||
pub mod stackstack;
|
||||
pub mod instr;
|
||||
pub mod vm;
|
||||
pub mod util;
|
||||
|
||||
extern crate alloc;
|
||||
234
hyphae/src/stackstack.rs
Normal file
234
hyphae/src/stackstack.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
use core::ops::Index;
|
||||
use alloc::rc::Rc;
|
||||
|
||||
struct StackInner<T: Sized> {
|
||||
pub next: Stack<T>,
|
||||
pub data: T
|
||||
}
|
||||
|
||||
struct Stack<T: Sized> (Rc<Option<StackInner<T>>>);
|
||||
|
||||
struct StackStackInner<T: Sized> {
|
||||
next: StackStack<T>,
|
||||
count: usize,
|
||||
stack: Stack<T>,
|
||||
}
|
||||
|
||||
pub struct StackStack<T: Sized> (Rc<Option<StackStackInner<T>>>);
|
||||
|
||||
impl<T> From<T> for StackInner<T> {
|
||||
fn from(t: T) -> StackInner<T> {
|
||||
StackInner {
|
||||
next: Stack(Rc::from(None)),
|
||||
data: t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<StackInner<T>> for Stack<T> {
|
||||
fn from(t: StackInner<T>) -> Stack<T> {
|
||||
Stack(Rc::from(Some(t)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for StackStack<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: usize) -> &T {
|
||||
if let Some(ref inner) = *self.0 {
|
||||
// pass on to next
|
||||
if inner.count <= index {
|
||||
&inner.next[index - inner.count]
|
||||
|
||||
// fetch from our stack
|
||||
} else {
|
||||
let mut idx = index;
|
||||
let mut cursor = &inner.stack;
|
||||
while let Some(ref node) = *cursor.0 {
|
||||
if idx == 0 {
|
||||
return &node.data
|
||||
}
|
||||
idx -= 1;
|
||||
cursor = &node.next;
|
||||
}
|
||||
// should never hit this case
|
||||
panic!("encountered inconsistent lengths in stackstack")
|
||||
}
|
||||
|
||||
// guaranteed out of bounds
|
||||
} else {
|
||||
panic!("index out of bounds on stackstack access")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for StackStack<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut ss_idx = 1;
|
||||
let mut ss_cur = &*self.0;
|
||||
while let Some(inner) = ss_cur {
|
||||
write!(f, "Frame {ss_idx}:")?;
|
||||
let mut s_cur = &*inner.stack.0;
|
||||
while let Some(node) = s_cur {
|
||||
write!(f, " {:#?}", node.data)?;
|
||||
s_cur = &*node.next.0;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
ss_cur = &*inner.next.0;
|
||||
ss_idx += 1;
|
||||
}
|
||||
|
||||
write!(f, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Stack<T> {
|
||||
fn push(&mut self, item: T) {
|
||||
self.0 = Rc::from(Some(StackInner{
|
||||
data: item,
|
||||
next: Stack(self.0.clone()),
|
||||
}))
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> T {
|
||||
// clone self.0 and then drop first ref, decreasing strong count back to 1
|
||||
let d = self.0.clone();
|
||||
self.0 = Rc::new(None);
|
||||
|
||||
// deconstruct the rc that formerly held self.0
|
||||
let b = Rc::into_inner(d).unwrap();
|
||||
if let Some(inner) = b {
|
||||
let data = inner.data;
|
||||
self.0 = inner.next.0;
|
||||
data
|
||||
} else {
|
||||
panic!("pop from 0 length stack")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StackStack<T> {
|
||||
pub fn push_current_stack(&mut self, item: T) {
|
||||
if let Some(inner) = Rc::get_mut(&mut self.0).unwrap() {
|
||||
inner.stack.push(item);
|
||||
inner.count += 1;
|
||||
} else {
|
||||
panic!("push to uninitialized stackstack")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_current_stack(&mut self) -> T {
|
||||
if let Some(inner) = Rc::get_mut(&mut self.0).unwrap() {
|
||||
inner.count -= 1;
|
||||
inner.stack.pop()
|
||||
} else {
|
||||
panic!("pop from uninitialized stackstack")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_stack(&mut self) {
|
||||
self.0 = Rc::from(Some(StackStackInner{
|
||||
next: StackStack(self.0.clone()),
|
||||
count: 0,
|
||||
stack: Stack(Rc::from(None)),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn destroy_top_stack(&mut self) {
|
||||
let s = Rc::get_mut(&mut self.0).unwrap();
|
||||
if let Some(inner) = s {
|
||||
self.0 = inner.next.0.clone()
|
||||
} else {
|
||||
panic!("del from empty stackstack")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> StackStack<T> {
|
||||
StackStack(Rc::from(Some(StackStackInner{
|
||||
count: 0,
|
||||
next: StackStack(Rc::from(None)),
|
||||
stack: Stack(Rc::from(None)),
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
if let Some(ref inner) = *self.0 {
|
||||
if let Some(_) = *inner.next.0 {
|
||||
inner.next.len() + inner.count
|
||||
} else {
|
||||
inner.count
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_alloc_new_stack_and_push_many() {
|
||||
let mut g = StackStack::<i8>::new();
|
||||
g.add_stack();
|
||||
g.push_current_stack(0);
|
||||
g.push_current_stack(1);
|
||||
g.push_current_stack(2);
|
||||
assert_eq!(g.len(), 3);
|
||||
g.add_stack();
|
||||
g.push_current_stack(3);
|
||||
g.push_current_stack(4);
|
||||
assert_eq!(g.len(), 5);
|
||||
|
||||
assert_eq!(g.pop_current_stack(), 4);
|
||||
assert_eq!(g.pop_current_stack(), 3);
|
||||
g.destroy_top_stack();
|
||||
assert_eq!(g.pop_current_stack(), 2);
|
||||
assert_eq!(g.pop_current_stack(), 1);
|
||||
assert_eq!(g.pop_current_stack(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stack_index_bounds() {
|
||||
let mut g = StackStack::<i8>::new();
|
||||
g.add_stack();
|
||||
g.push_current_stack(0);
|
||||
g.push_current_stack(1);
|
||||
g.push_current_stack(2);
|
||||
assert_eq!(g.len(), 3);
|
||||
g.add_stack();
|
||||
g.push_current_stack(3);
|
||||
g.push_current_stack(4);
|
||||
assert_eq!(g.len(), 5);
|
||||
|
||||
assert_eq!(g[0], 4);
|
||||
assert_eq!(g[1], 3);
|
||||
assert_eq!(g[2], 2);
|
||||
assert_eq!(g[3], 1);
|
||||
assert_eq!(g[4], 0);
|
||||
|
||||
g.destroy_top_stack();
|
||||
assert_eq!(g.len(), 3);
|
||||
assert_eq!(g[0], 2);
|
||||
assert_eq!(g[1], 1);
|
||||
assert_eq!(g[2], 0);
|
||||
}
|
||||
}
|
||||
303
hyphae/src/util.rs
Normal file
303
hyphae/src/util.rs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
use crate::instr::Operation;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloc::vec;
|
||||
|
||||
use core::ops::Index;
|
||||
use core::mem::transmute;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Address {
|
||||
Stack = 0xf0, // immutable access only
|
||||
Instr = 0xf1, // immutable access only
|
||||
Expr = 0xf2, // mutable access allowed
|
||||
Oper1 = 0xf3, // mutable access allowed
|
||||
Oper2 = 0xf4, // mutable access allowed
|
||||
Oper3 = 0xf5, // mutable access allowed
|
||||
Oper4 = 0xf6, // mutable access allowed
|
||||
Numer = 0xf8, // immutable access only
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Operand(pub Address, pub usize);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Instruction(pub Operation, pub Vec<Operand>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Program(pub Vec<Instruction>);
|
||||
|
||||
impl Into<u8> for Address {
|
||||
fn into(self) -> u8 {
|
||||
unsafe { transmute::<Address, u8>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Address {
|
||||
type Error = &'static str;
|
||||
fn try_from(val: u8) -> Result<Self, Self::Error> {
|
||||
match val {
|
||||
_ if val == Address::Stack as u8 => Ok(Address::Stack),
|
||||
_ if val == Address::Instr as u8 => Ok(Address::Instr),
|
||||
_ if val == Address::Expr as u8 => Ok(Address::Expr),
|
||||
_ if val == Address::Oper1 as u8 => Ok(Address::Oper1),
|
||||
_ if val == Address::Oper2 as u8 => Ok(Address::Oper2),
|
||||
_ if val == Address::Oper3 as u8 => Ok(Address::Oper3),
|
||||
_ if val == Address::Oper4 as u8 => Ok(Address::Oper4),
|
||||
_ if val == Address::Numer as u8 => Ok(Address::Numer),
|
||||
_ => Err("illegal addressing mode")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Address {
|
||||
fn operand_size(&self) -> u8 {
|
||||
match self {
|
||||
Address::Stack => (usize::BITS / 8) as u8,
|
||||
Address::Instr => (usize::BITS / 8) as u8,
|
||||
Address::Numer => (usize::BITS / 8) as u8,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Operand {
|
||||
type Error = &'static str;
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
let addr_mode: Address = value[0].try_into()?;
|
||||
let operand_size = addr_mode.operand_size();
|
||||
if value.len() < (operand_size + 1).into() {
|
||||
return Err("truncated address data")
|
||||
}
|
||||
|
||||
let mut operand_bytes: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for (&src, dest) in value[1..(1+operand_size) as usize]
|
||||
.iter()
|
||||
.zip(operand_bytes.iter_mut()) {
|
||||
*dest = src;
|
||||
}
|
||||
|
||||
Ok(Operand(addr_mode, usize::from_ne_bytes(operand_bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for Operand {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut res = vec![];
|
||||
res.push(self.0.clone() as u8);
|
||||
res.append(&mut self.1.to_ne_bytes()[..self.0.operand_size() as usize].to_vec());
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
fn byte_length(&self) -> u8 {
|
||||
1 + self.0.operand_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Instruction {
|
||||
type Error = &'static str;
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
let operation: Operation = value[0].try_into()?;
|
||||
let mut operands: Vec<Operand> = vec![];
|
||||
|
||||
let mut cur = 1;
|
||||
for _ in 0..operation.num_args()? {
|
||||
if cur >= value.len() {
|
||||
return Err("operand data truncated")
|
||||
}
|
||||
let operand: Operand = value[cur..].try_into()?;
|
||||
cur += operand.byte_length() as usize;
|
||||
operands.push(operand);
|
||||
}
|
||||
|
||||
Ok(Instruction(operation, operands))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for Instruction {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut res = vec![];
|
||||
res.push(self.0.0);
|
||||
for op in self.1 {
|
||||
res.append(&mut op.into())
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
fn byte_length(&self) -> u8 {
|
||||
self.1.iter()
|
||||
.fold(0, |total, oper|
|
||||
total + oper.byte_length()) + 1
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for Program {
|
||||
type Error = &'static str;
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
let mut prog: Vec<Instruction> = vec![];
|
||||
let mut cur = 0;
|
||||
|
||||
while cur < value.len() {
|
||||
let instruction: Instruction = value[cur..].try_into()?;
|
||||
cur += instruction.byte_length() as usize;
|
||||
prog.push(instruction);
|
||||
}
|
||||
|
||||
Ok(Program(prog))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for Program {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut res: Vec<u8> = vec![];
|
||||
for instr in self.0 {
|
||||
res.append(&mut instr.into())
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Index<usize> for Program {
|
||||
type Output = Instruction;
|
||||
fn index(&self, index: usize) -> &Instruction {
|
||||
self.0.get(index).expect("access to out of bounds instruction in vm")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::instr;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_operand_parse() {
|
||||
let bad_addressing =
|
||||
TryInto::<Operand>::try_into(&[0x13, 0x39][..]);
|
||||
assert_eq!(bad_addressing, Err("illegal addressing mode"));
|
||||
|
||||
let truncated_address =
|
||||
TryInto::<Operand>::try_into(&[0xf1][..]);
|
||||
assert_eq!(truncated_address, Err("truncated address data"));
|
||||
|
||||
let usize_case =
|
||||
TryInto::<Operand>::try_into(&[Address::Stack.into(),
|
||||
0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23][..]);
|
||||
assert!(usize_case.is_ok());
|
||||
assert_eq!(usize_case.unwrap().0, Address::Stack);
|
||||
|
||||
let register_operand = Operand(Address::Expr, 0);
|
||||
let operand_byte_arr =
|
||||
TryInto::<Vec<u8>>::try_into(register_operand.clone());
|
||||
assert!(operand_byte_arr.is_ok());
|
||||
let br = operand_byte_arr.unwrap();
|
||||
let operand_bytes = br.as_slice();
|
||||
assert_eq!(operand_bytes, &[0xf2][..]);
|
||||
let operand_conv =
|
||||
TryInto::<Operand>::try_into(operand_bytes);
|
||||
assert!(operand_conv.is_ok());
|
||||
assert_eq!(register_operand, operand_conv.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instruction_parse() {
|
||||
let illegal_instruction =
|
||||
TryInto::<Instruction>::try_into(&[0x88][..]);
|
||||
assert_eq!(illegal_instruction, Err("illegal instruction"));
|
||||
|
||||
let bad_operand =
|
||||
TryInto::<Instruction>::try_into(&[instr::TRAP.0, 0xf1][..]);
|
||||
assert_eq!(bad_operand, Err("truncated address data"));
|
||||
|
||||
let need_more_opers =
|
||||
TryInto::<Instruction>::try_into(&[instr::TRAP.0][..]);
|
||||
assert_eq!(need_more_opers, Err("operand data truncated"));
|
||||
|
||||
let no_operands =
|
||||
TryInto::<Instruction>::try_into(&[instr::POP.0][..]);
|
||||
assert!(no_operands.is_ok());
|
||||
let nop = no_operands.unwrap();
|
||||
assert_eq!(nop.0, instr::POP);
|
||||
let nop_bytes =
|
||||
TryInto::<Vec<u8>>::try_into(nop);
|
||||
assert!(nop_bytes.is_ok());
|
||||
assert_eq!(nop_bytes.unwrap(), vec![instr::POP.0]);
|
||||
|
||||
let one_operand =
|
||||
TryInto::<Instruction>::try_into(&[instr::TRAP.0, 0xf3][..]);
|
||||
assert!(one_operand.is_ok());
|
||||
let oe_oper = one_operand.unwrap();
|
||||
assert_eq!(oe_oper.0, instr::TRAP);
|
||||
assert_eq!(oe_oper.1.len(), 1);
|
||||
assert_eq!(oe_oper.1[0], Operand(Address::Oper1, 0));
|
||||
let oe_bytes =
|
||||
TryInto::<Vec<u8>>::try_into(oe_oper);
|
||||
assert!(oe_bytes.is_ok());
|
||||
assert_eq!(oe_bytes.unwrap(), vec![instr::TRAP.0, 0xf3]);
|
||||
|
||||
let two_operands =
|
||||
TryInto::<Instruction>::try_into(&[instr::LOAD.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.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_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,
|
||||
vec![Operand(Address::Oper1, 0), Operand(Address::Oper2, 0)])];
|
||||
let res1 =
|
||||
TryInto::<Program>::try_into(&bytes1[..]);
|
||||
assert!(res1.is_ok());
|
||||
assert_eq!(res1.unwrap().0, out1);
|
||||
|
||||
let bytes2 = [
|
||||
instr::LOAD.0, 0xf3, 0xf4,
|
||||
instr::CLEAR.0, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
];
|
||||
let out2 = vec![
|
||||
Instruction(instr::LOAD, vec![
|
||||
Operand(Address::Oper1, 0),
|
||||
Operand(Address::Oper2, 0)
|
||||
]),
|
||||
|
||||
Instruction(instr::CLEAR, vec![
|
||||
Operand(Address::Stack, 1)
|
||||
])
|
||||
];
|
||||
let res2 =
|
||||
TryInto::<Program>::try_into(&bytes2[..]);
|
||||
assert!(res2.is_ok());
|
||||
assert_eq!(res2.unwrap().0, out2);
|
||||
}
|
||||
}
|
||||
453
hyphae/src/vm.rs
Normal file
453
hyphae/src/vm.rs
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
use mycelium::sexpr::Datum;
|
||||
use mycelium::number::{Fraction, Number, Numeric};
|
||||
|
||||
use crate::hmap::QuickMap;
|
||||
use crate::stackstack::StackStack;
|
||||
use crate::instr as i;
|
||||
use crate::util::{Operand, Program, Address};
|
||||
|
||||
use core::cell::RefCell;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::rc::Rc;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::borrow::ToOwned;
|
||||
|
||||
use num::pow::Pow;
|
||||
|
||||
|
||||
const NUM_OPERAND_REGISTERS: usize = 4;
|
||||
|
||||
pub struct VM {
|
||||
// execution environment
|
||||
pub stack: StackStack<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],
|
||||
|
||||
// control flow registers
|
||||
pub retn: usize,
|
||||
pub ictr: usize,
|
||||
pub errr: Datum,
|
||||
|
||||
// state
|
||||
pub running: bool,
|
||||
pub err_state: bool,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
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) {
|
||||
let instr = &self.prog.0[self.ictr].clone();
|
||||
|
||||
macro_rules! e {
|
||||
( $err:expr ) => {
|
||||
{
|
||||
self.running = false;
|
||||
self.err_state = true;
|
||||
self.errr = Datum::String($err.as_bytes().to_vec());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! deref {
|
||||
( $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],
|
||||
Address::Numer => e!("attempt to dereference constant numeric data"),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
self.ictr = target;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! lr_oper {
|
||||
( $in_type:ident, $oper:tt, $out_type:ident ) => {
|
||||
self.expr = Datum::$out_type(*match deref!(&instr.1[0]){
|
||||
Datum::$in_type(l) => l,
|
||||
_ => e!("illegal argument to instruction"),
|
||||
} $oper *match deref!(&instr.1[1]){
|
||||
Datum::$in_type(l) => l,
|
||||
_ => e!("illegal argument to instruction"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 => {
|
||||
let Datum::String(tag) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to BIND instruction");
|
||||
};
|
||||
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 {
|
||||
e!("illegal argument to UNBIND instruction");
|
||||
};
|
||||
let tag = unsafe { str::from_utf8_unchecked(&tag) };
|
||||
self.symtab.remove(&tag);
|
||||
},
|
||||
|
||||
i::BOUND => {
|
||||
let Datum::String(tag) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to BOUND instruction");
|
||||
};
|
||||
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::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,
|
||||
|
||||
// control flow ops
|
||||
i::NOP => (),
|
||||
i::HALT => self.running = false,
|
||||
i::PANIC => {
|
||||
self.running = false;
|
||||
self.err_state = false;
|
||||
self.errr = deref!(&instr.1[0]).clone()
|
||||
},
|
||||
|
||||
i::JMP => {
|
||||
do_jmp!(0);
|
||||
},
|
||||
|
||||
i::JMPIF => {
|
||||
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::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 {
|
||||
e!("illegal argument to BOOL_NOT instruction");
|
||||
};
|
||||
a
|
||||
});
|
||||
},
|
||||
|
||||
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(!{
|
||||
let Datum::Char(a) = self.expr else {
|
||||
e!("illegal argument to BYTE_NOT instruction");
|
||||
};
|
||||
a
|
||||
});
|
||||
},
|
||||
|
||||
// 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 => {
|
||||
let Datum::Number(l) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to IDIV instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(r) = deref!(&instr.1[1]) else {
|
||||
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");
|
||||
};
|
||||
|
||||
self.expr = Datum::Number(Number::Fra(Fraction(l / r, 1)));
|
||||
},
|
||||
|
||||
i::POW => {
|
||||
let Datum::Number(l) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to POW instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(r) = deref!(&instr.1[1]) else {
|
||||
e!("illgal argument to POW instruction");
|
||||
};
|
||||
|
||||
self.expr = Datum::Number((*l).pow(*r));
|
||||
},
|
||||
|
||||
i::INC => if let Datum::Number(src) = deref_mut!(&instr.1[0]) {
|
||||
*src = *src + Number::Fra(Fraction(1, 1));
|
||||
} 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));
|
||||
} 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)));
|
||||
} else {
|
||||
e!("illegal argument to CTON instruction");
|
||||
}
|
||||
},
|
||||
|
||||
i::NTOC => {
|
||||
let src = deref_mut!(&instr.1[0]);
|
||||
if let Datum::Number(snum) = src {
|
||||
let n = snum.make_inexact();
|
||||
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);
|
||||
|
||||
} else {
|
||||
e!("illegal argument to NTOC instruction");
|
||||
}
|
||||
},
|
||||
|
||||
i::MKVEC => self.expr = Datum::Vector(RefCell::from(vec![])),
|
||||
i::MKBVEC => self.expr = Datum::ByteVector(RefCell::from(vec![])),
|
||||
i::INDEX => {
|
||||
let Datum::Number(idx) = deref!(&instr.1[1]) else {
|
||||
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;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
let a = (*v.borrow()[idx].clone()).clone();
|
||||
self.expr = a;
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let a = Datum::Char(bv.borrow()[idx]);
|
||||
self.expr = a;
|
||||
},
|
||||
Datum::List(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;
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let a = Datum::Number(Number::Fra(Fraction(bv.borrow().len() as isize, 1)));
|
||||
self.expr = a;
|
||||
},
|
||||
Datum::List(l) =>
|
||||
self.expr = Datum::Number(Number::Fra(Fraction(l.len() as isize, 1))),
|
||||
_ => e!("illegal argument to LENGTH instruction"),
|
||||
},
|
||||
|
||||
i::SUBSL => {
|
||||
let Datum::Number(st) = deref!(&instr.1[1]) else {
|
||||
e!("illegal argument to SUBSL instruction");
|
||||
};
|
||||
|
||||
let Datum::Number(ed) = deref!(&instr.1[2]) else {
|
||||
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;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
let a = Datum::Vector(RefCell::from(v.borrow()[st..ed].to_vec()));
|
||||
self.expr = a;
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let a = Datum::ByteVector(RefCell::from(bv.borrow()[st..ed].to_vec()));
|
||||
self.expr = a;
|
||||
},
|
||||
Datum::List(a) =>
|
||||
self.expr = Datum::List(Rc::new(
|
||||
(**a).subsl(st as isize, ed as isize))),
|
||||
_ => e!("illegal argument to SUBSL instruction")
|
||||
};
|
||||
}
|
||||
|
||||
i::INSER => {
|
||||
let Datum::Number(idx) = deref!(&instr.1[2]) else {
|
||||
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;
|
||||
|
||||
match deref!(&instr.1[0]) {
|
||||
Datum::Vector(v) => {
|
||||
v.borrow_mut().insert(idx, deref!(&instr.1[1]).clone().into());
|
||||
},
|
||||
Datum::ByteVector(bv) => {
|
||||
let Datum::Char(b) = deref!(&instr.1[1]) else {
|
||||
e!("INSER instruction can only insert a byte into a bytevector");
|
||||
};
|
||||
bv.borrow_mut().insert(idx, *b);
|
||||
},
|
||||
_ => e!("illegal argument to INSER instruction")
|
||||
}
|
||||
},
|
||||
|
||||
i::CAR => {
|
||||
let Datum::List(arg) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to CAR instruction");
|
||||
};
|
||||
|
||||
self.expr = (*arg.0).clone();
|
||||
},
|
||||
|
||||
i::CDR => {
|
||||
let Datum::List(arg) = deref!(&instr.1[0]) else {
|
||||
e!("illegal argument to CAR instruction");
|
||||
};
|
||||
|
||||
self.expr = (*arg.0).clone();
|
||||
},
|
||||
|
||||
i::CONS => todo!("implement AST API"),
|
||||
i::PARSE => todo!("implement AST API"),
|
||||
i::EVAL => todo!("implement AST API"),
|
||||
|
||||
_ => {
|
||||
e!("illegal instruction");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue