HyphaeVM - WIP
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. Of note, the ISA is mostly finished and much of the VM design is in place. Yet to be done are a few traps in mycelium, migrating pieces like the number package and the sexpr package into the VM package, and of course much testing. Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
3a0a141738
commit
4ad319213d
17 changed files with 2073 additions and 17 deletions
12
hyphae/Cargo.toml
Normal file
12
hyphae/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "hyphae"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
mycelium = { path = "../mycelium" }
|
||||
num = { version = "0.4.3", features = ["alloc"] }
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1.0", features = ["alloc", "derive"] }
|
||||
toml = "0.8.23"
|
||||
109
hyphae/build.rs
Normal file
109
hyphae/build.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use std::{env, fs};
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Document {
|
||||
pub instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
// dont warn about unused fields in json instruction struct
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize)]
|
||||
struct Instruction {
|
||||
pub name: String,
|
||||
pub args: Vec<String>,
|
||||
pub output: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let output_path = Path::new(&env::var("OUT_DIR").unwrap())
|
||||
.join("hyphae_instr.rs");
|
||||
let input = fs::read_to_string("instructions.toml")
|
||||
.unwrap();
|
||||
let mut output_file =
|
||||
BufWriter::new(File::create(&output_path).unwrap());
|
||||
|
||||
let instruction_table: Document =
|
||||
toml::from_str(&input)
|
||||
.expect("hyphae: failed to parse instructions.toml");
|
||||
|
||||
let mut isa = "#[repr(transparent)]\n".to_owned();
|
||||
isa += "#[derive(Clone, Debug, PartialEq)]\n";
|
||||
isa += "pub struct Operation(pub u8);\n\n";
|
||||
|
||||
let mut isa_from_byte = "impl TryFrom<u8> for Operation {\n".to_owned();
|
||||
isa_from_byte += " type Error = &'static str;\n";
|
||||
isa_from_byte += " fn try_from(v: u8) -> Result<Self, Self::Error> {\n";
|
||||
isa_from_byte += " match v {\n";
|
||||
|
||||
|
||||
let mut isa_fromstr = "impl FromStr for Operation {\n".to_owned();
|
||||
isa_fromstr += " type Err = &'static str;\n";
|
||||
isa_fromstr += " fn from_str(v: &str) -> Result<Self, Self::Err> {\n";
|
||||
isa_fromstr += " match v {\n";
|
||||
|
||||
let mut isa_from_str = "impl TryFrom<&str> for Operation {\n".to_owned();
|
||||
isa_from_str += " type Error = &'static str;\n";
|
||||
isa_from_str += " fn try_from(v: &str) -> Result<Self, Self::Error> {\n";
|
||||
isa_from_str += " match v {\n";
|
||||
|
||||
let mut isa_num_args = "impl Operation {\n".to_owned();
|
||||
isa_num_args += " pub fn num_args(&self) -> Result<u8, &'static str> {\n";
|
||||
isa_num_args += " match self.0 {\n";
|
||||
|
||||
instruction_table.instructions.iter()
|
||||
.enumerate()
|
||||
.for_each(|(idx, instr)| {
|
||||
let const_name = instr.name.to_ascii_uppercase();
|
||||
|
||||
isa += format!("pub const {}: Operation = Operation({});\n",
|
||||
const_name, idx).as_str();
|
||||
|
||||
isa_from_byte += format!(" {} => Ok({}),\n", idx, const_name)
|
||||
.as_str();
|
||||
|
||||
isa_from_str += format!(" \"{}\" => Ok({}),\n",
|
||||
const_name, const_name).as_str();
|
||||
|
||||
isa_fromstr += format!(" \"{}\" => Ok({}),\n",
|
||||
const_name, const_name).as_str();
|
||||
|
||||
isa_num_args += format!(" {} => Ok({}),\n", idx, instr.args.len())
|
||||
.as_str()
|
||||
});
|
||||
|
||||
isa_from_byte += " _ => Err(\"illegal instruction\"),\n";
|
||||
isa_from_byte += " }\n";
|
||||
isa_from_byte += " }\n";
|
||||
isa_from_byte += "}\n\n";
|
||||
|
||||
isa_from_str += " _ => Err(\"illegal instruction\"),\n";
|
||||
isa_from_str += " }\n";
|
||||
isa_from_str += " }\n";
|
||||
isa_from_str += "}\n\n";
|
||||
|
||||
isa_fromstr += " _ => Err(\"illegal instruction\"),\n";
|
||||
isa_fromstr += " }\n";
|
||||
isa_fromstr += " }\n";
|
||||
isa_fromstr += "}\n\n";
|
||||
|
||||
isa_num_args += " _ => Err(\"illegal instruction\"),\n";
|
||||
isa_num_args += " }\n";
|
||||
isa_num_args += " }\n";
|
||||
isa_num_args += "}\n\n";
|
||||
|
||||
isa += "\n";
|
||||
isa += isa_from_byte.as_str();
|
||||
isa += isa_from_str.as_str();
|
||||
isa += isa_fromstr.as_str();
|
||||
isa += isa_num_args.as_str();
|
||||
|
||||
write!(&mut output_file, "use core::str::FromStr;\n\n\n").unwrap();
|
||||
write!(&mut output_file, "{}", isa).unwrap();
|
||||
println!("cargo::rerun-if-changed=build.rs");
|
||||
println!("cargo::rerun-if-changed=instructions.json");
|
||||
}
|
||||
303
hyphae/instructions.toml
Normal file
303
hyphae/instructions.toml
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
# NOTE: keep libc out of this, thats what trap vector is for
|
||||
# NOTE: to programmers: only registers allow mutable acess
|
||||
|
||||
[[instructions]]
|
||||
name = "trap"
|
||||
args = ["index"]
|
||||
output = "result of function"
|
||||
description = "triggers callback in trap vector at index"
|
||||
|
||||
[[instructions]]
|
||||
name = "bind"
|
||||
args = ["name", "operand"]
|
||||
output = ""
|
||||
description = "map name to operand in sym table."
|
||||
|
||||
[[instructions]]
|
||||
name = "unbind"
|
||||
args = ["name"]
|
||||
output = ""
|
||||
description = "remove name mapping from sym table."
|
||||
|
||||
[[instructions]]
|
||||
name = "bound"
|
||||
args = ["name"]
|
||||
output = "expr = true if name is bound"
|
||||
description = "test if a name is already bound"
|
||||
|
||||
[[instructions]]
|
||||
name = "push"
|
||||
args = ["operand"]
|
||||
output = ""
|
||||
description = "pushes operand onto stack."
|
||||
|
||||
[[instructions]]
|
||||
name = "pop"
|
||||
args = []
|
||||
output = ""
|
||||
description = "removes element at top of stack."
|
||||
|
||||
[[instructions]]
|
||||
name = "enter"
|
||||
args = []
|
||||
output = ""
|
||||
description = "create new stack frame"
|
||||
|
||||
[[instructions]]
|
||||
name = "exit"
|
||||
args = []
|
||||
output = ""
|
||||
description = "delete current stack frame"
|
||||
|
||||
[[instructions]]
|
||||
name = "load"
|
||||
args = ["src", "dest"]
|
||||
output = ""
|
||||
description = "copies src into dest"
|
||||
|
||||
[[instructions]]
|
||||
name = "clear"
|
||||
args = ["dest"]
|
||||
output = ""
|
||||
description = "clears dest"
|
||||
|
||||
[[instructions]]
|
||||
name = "nop"
|
||||
args = []
|
||||
output = ""
|
||||
description = "no operation"
|
||||
|
||||
[[instructions]]
|
||||
name = "halt"
|
||||
args = []
|
||||
output = ""
|
||||
description = "halts the VM"
|
||||
|
||||
[[instructions]]
|
||||
name = "panic"
|
||||
args = ["error"]
|
||||
output = ""
|
||||
description = "sets error state and halts VM"
|
||||
|
||||
[[instructions]]
|
||||
name = "jmp"
|
||||
args = ["addr"]
|
||||
output = ""
|
||||
description = "sets ictr register to addr"
|
||||
|
||||
[[instructions]]
|
||||
name = "jmpif"
|
||||
args = ["addr"]
|
||||
output = ""
|
||||
description = "if expr register holds true, sets ictr to addr"
|
||||
|
||||
[[instructions]]
|
||||
name = "eq"
|
||||
args = ["a", "b"]
|
||||
output = "a == b"
|
||||
description = "equality test"
|
||||
|
||||
[[instructions]]
|
||||
name = "lt"
|
||||
args = ["a", "b"]
|
||||
output = "a < b"
|
||||
description = "less than test"
|
||||
|
||||
[[instructions]]
|
||||
name = "gt"
|
||||
args = ["a", "b"]
|
||||
output = "a > b"
|
||||
description = "greater than test"
|
||||
|
||||
[[instructions]]
|
||||
name = "lte"
|
||||
args = ["a", "b"]
|
||||
output = "a <= b"
|
||||
description = "less than equals test"
|
||||
|
||||
[[instructions]]
|
||||
name = "gte"
|
||||
args = ["a", "b"]
|
||||
output = "a >= b"
|
||||
description = "greater than equals test"
|
||||
|
||||
[[instructions]]
|
||||
name = "bool_not"
|
||||
args = []
|
||||
output = "expr = !expr"
|
||||
description = "boolean not"
|
||||
|
||||
[[instructions]]
|
||||
name = "bool_and"
|
||||
args = ["a", "b"]
|
||||
output = "a && b"
|
||||
description = "boolean and"
|
||||
|
||||
[[instructions]]
|
||||
name = "bool_or"
|
||||
args = ["a", "b"]
|
||||
output = "a || b"
|
||||
description = "boolean or"
|
||||
|
||||
[[instructions]]
|
||||
name = "byte_and"
|
||||
args = ["a", "b"]
|
||||
output = "a & b"
|
||||
description = "bitwise and"
|
||||
|
||||
[[instructions]]
|
||||
name = "byte_or"
|
||||
args = ["a", "b"]
|
||||
output = "a | b"
|
||||
description = "bitwise or"
|
||||
|
||||
[[instructions]]
|
||||
name = "xor"
|
||||
args = ["a", "b"]
|
||||
output = "a xor b"
|
||||
description = "bitwise exclusive or"
|
||||
|
||||
[[instructions]]
|
||||
name = "byte_not"
|
||||
args = []
|
||||
output = "expr = !expr"
|
||||
description = "bitwise not"
|
||||
|
||||
[[instructions]]
|
||||
name = "add"
|
||||
args = ["a", "b"]
|
||||
output = "a + b"
|
||||
description = "numeric addition"
|
||||
|
||||
[[instructions]]
|
||||
name = "sub"
|
||||
args = ["a", "b"]
|
||||
output = "a - b"
|
||||
description = "numeric subtraction"
|
||||
|
||||
[[instructions]]
|
||||
name = "mul"
|
||||
args = ["a", "b"]
|
||||
output = "a * b"
|
||||
description = "numeric multiplication"
|
||||
|
||||
[[instructions]]
|
||||
name = "fdiv"
|
||||
args = ["a", "b"]
|
||||
output = "a / b"
|
||||
description = "numeric FLOAT division"
|
||||
|
||||
[[instructions]]
|
||||
name = "idiv"
|
||||
args = ["a", "b"]
|
||||
output = "a / b"
|
||||
description = "numeric INTEGER division"
|
||||
|
||||
[[instructions]]
|
||||
name = "pow"
|
||||
args = ["a", "b"]
|
||||
output = "a ^ b"
|
||||
description = "numeric operation to raise a to the power of b"
|
||||
|
||||
[[instructions]]
|
||||
name = "modulo"
|
||||
args = ["a", "b"]
|
||||
output = "a % b"
|
||||
description = "numeric modulo operation"
|
||||
|
||||
[[instructions]]
|
||||
name = "rem"
|
||||
args = ["a", "b"]
|
||||
output = "remainder from a / b"
|
||||
description = "remainder from integer division"
|
||||
|
||||
[[instructions]]
|
||||
name = "inc"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "increments number at source"
|
||||
|
||||
[[instructions]]
|
||||
name = "dec"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "decrements number at source"
|
||||
|
||||
[[instructions]]
|
||||
name = "cton"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "mutates a char datum into a number datum"
|
||||
|
||||
[[instructions]]
|
||||
name = "ntoc"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "mutates a number datum into a char datum"
|
||||
|
||||
[[instructions]]
|
||||
name = "ntoi"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "mutates a number datum into its exact form"
|
||||
|
||||
[[instructions]]
|
||||
name = "ntoe"
|
||||
args = ["src"]
|
||||
output = ""
|
||||
description = "mutates a number datum into its inexact form"
|
||||
|
||||
[[instructions]]
|
||||
name = "mkvec"
|
||||
args = []
|
||||
output = "a blank vector"
|
||||
description = "creates a new vector"
|
||||
|
||||
[[instructions]]
|
||||
name = "mkbvec"
|
||||
args = []
|
||||
output = "a blank bytevector"
|
||||
description = "creates a blank bytevector"
|
||||
|
||||
[[instructions]]
|
||||
name = "index"
|
||||
args = ["collection", "index"]
|
||||
output = "collection[index]"
|
||||
description = "extracts element from collection at index"
|
||||
|
||||
[[instructions]]
|
||||
name = "length"
|
||||
args = ["collection"]
|
||||
output = "length of collection"
|
||||
description = "calculates length of collection"
|
||||
|
||||
[[instructions]]
|
||||
name = "subsl"
|
||||
args = ["collection", "start", "end"]
|
||||
output = "collection[start:end]"
|
||||
description = "returns a subset from collection denoted by start and end indexes"
|
||||
|
||||
[[instructions]]
|
||||
name = "inser"
|
||||
args = ["collection", "elem", "idx"]
|
||||
output = ""
|
||||
description = "inserts an element at specified index into a collection"
|
||||
|
||||
[[instructions]]
|
||||
name = "cons"
|
||||
args = ["left", "right"]
|
||||
output = "resulting collection"
|
||||
description = "either append right to left or make new list from both"
|
||||
|
||||
[[instructions]]
|
||||
name = "car"
|
||||
args = ["list"]
|
||||
output = "returns first element in cons cell"
|
||||
description = "takes an AST and returns first element in top level cons cell"
|
||||
|
||||
[[instructions]]
|
||||
name = "cdr"
|
||||
args = ["list"]
|
||||
output = "returns last element in cons cell"
|
||||
description = "takes an AST and returns last element in top level cons cell"
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
460
hyphae/src/vm.rs
Normal file
460
hyphae/src/vm.rs
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
/* 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.1).clone();
|
||||
},
|
||||
|
||||
i::CONS => {
|
||||
/* CONS BEHAVIOR
|
||||
* L Datum is not list means create a new standard form list
|
||||
* L Datum is list then append the second element to the first
|
||||
*/
|
||||
},
|
||||
|
||||
// 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");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue