Hyphae: add assembler and disassembler
Some checks failed
per-push tests / timed-decomposer-parse (push) Blocked by required conditions
per-push tests / test-backend (push) Blocked by required conditions
per-push tests / build (push) Successful in 1m50s
per-push tests / test-utility (push) Has been cancelled
per-push tests / test-frontend (push) Has been cancelled

This commit adds hyphae binaries for an assembler and a disassembler
As well as some fixes for observed misbehavior during manual
verification.

The new binaries are hphc for compiling assmbly files and hphdump for
inspecting compiled hyphae binary.

Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2025-12-15 18:28:28 +00:00
parent 141ba43362
commit ad39e26945
5 changed files with 185 additions and 24 deletions

13
Cargo.lock generated
View file

@ -60,9 +60,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "clap"
version = "4.5.38"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
@ -70,9 +70,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.38"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@ -82,9 +82,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.32"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
@ -142,6 +142,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
name = "hyphae"
version = "0.1.0"
dependencies = [
"clap",
"num",
"organelle",
"serde",

View file

@ -3,9 +3,28 @@ name = "hyphae"
version = "0.1.0"
edition = "2024"
[lib]
name = "hyphae"
crate-type = ["lib"]
path = "src/lib.rs"
[[bin]]
name = "hphc"
path = "src/bin/hphc.rs"
required-features = ["cli"]
[[bin]]
name = "hphdump"
path = "src/bin/hphdump.rs"
required-features = ["cli"]
[features]
cli = ["clap"]
[dependencies]
organelle = { path = "../organelle" }
num = { version = "0.4.3", features = ["alloc"] }
clap = { version = "4.5.53", features = ["derive"], optional = true }
[build-dependencies]
serde = { version = "1.0", features = ["alloc", "derive"] }

77
hyphae/src/bin/hphc.rs Normal file
View file

@ -0,0 +1,77 @@
/* 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 hyphae;
use clap::Parser;
use std::path::PathBuf;
use std::fs::{read, File};
use std::io::Write;
use std::error::Error;
use std::str::FromStr;
const HPHC_SUFFIX: &str= "hph";
const HPHC_DEFAULT: &str = "out";
#[derive(Parser, Debug)]
#[command(version, about = "HyphaeVM assembly compiler")]
struct Args {
/// path to input program assembly
compile: PathBuf,
/// path for output bytecode
#[arg(short = 'o')]
output: Option<PathBuf>,
}
fn main() -> Result<(), Box<dyn Error>> {
// parse arguments
let args = Args::parse();
// set output filepath
let outfile = args.output
.or(Some({
let mut c = args.compile.clone();
if c.as_path().is_dir() || c.as_path().file_name().is_none() {
c = String::from(HPHC_DEFAULT).into();
} else {
c = c.as_path().file_name().unwrap().into();
}
c.set_extension(HPHC_SUFFIX);
c
}))
.unwrap();
// check input validity
if args.compile.as_path().is_dir() {
panic!("will not compile directory!");
}
// compile input program
let input_bytes = read(args.compile)?;
let input = str::from_utf8(input_bytes.as_slice())?;
let program = hyphae::serializer::Program::from_str(input)?;
// dump program bytecode to output
let bytecode: Vec<u8> = program.into();
let mut out_file = File::create(outfile)?;
out_file.write_all(&bytecode)?;
Ok(())
}

52
hyphae/src/bin/hphdump.rs Normal file
View file

@ -0,0 +1,52 @@
/* 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 hyphae;
use clap::Parser;
use std::path::PathBuf;
use std::fs::read;
use std::error::Error;
#[derive(Parser, Debug)]
#[command(version, about = "HyphaeVM compiled binary inspector")]
struct Args {
/// path to input program
decompile: PathBuf,
}
fn main() -> Result<(), Box<dyn Error>> {
// parse arguments
let args = Args::parse();
// check input validity
if args.decompile.as_path().is_dir() {
panic!("cannot dump directory!");
}
// load input program
let program: hyphae::serializer::Program;
program = read(args.decompile)?
.as_slice()
.try_into()?;
// Display
println!("{}", program);
Ok(())
}

View file

@ -153,16 +153,16 @@ impl FromStr for Operand {
"$oper4" => Ok(Operand(Address::Oper4, 0)),
"true" => Ok(Operand(Address::Bool, 1)),
"false" => Ok(Operand(Address::Bool, 0)),
a if a.chars().nth(0).unwrap() == '%' &&
a.len() > 1 &&
a if a.len() > 1 &&
a.chars().nth(0).unwrap() == '%' &&
a[1..].parse::<usize>().is_ok() =>
Ok(Operand(Address::Stack, a[1..].parse::<usize>().unwrap())),
a if a.chars().nth(0).unwrap() == '@' &&
a.len() > 1 &&
a if a.len() > 1 &&
a.chars().nth(0).unwrap() == '@' &&
a[1..].parse::<usize>().is_ok() =>
Ok(Operand(Address::Instr, a[1..].parse::<usize>().unwrap())),
a if a.chars().nth(0).unwrap() == '\'' &&
a.len() == 3 &&
a if a.len() == 3 &&
a.chars().nth(0).unwrap() == '\'' &&
a.chars().nth(2).unwrap() == '\'' =>
Ok(Operand(Address::Char, a.chars().nth(1).unwrap() as usize)),
a if a.parse::<usize>().is_ok() =>
@ -232,7 +232,11 @@ impl Into<Vec<u8>> for Instruction {
impl FromStr for Instruction {
type Err = &'static str;
fn from_str(v: &str) -> Result<Self, Self::Err> {
let toks: Vec<&str> = v.trim().split(' ').collect();
let toks: Vec<&str> = v
.trim()
.split(' ')
.filter(|x| x.len() > 0)
.collect();
if toks.len() < 1 {
return Err("empty string");
}
@ -256,7 +260,7 @@ impl FromStr for Instruction {
impl Display for Instruction {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> {
write!(f, "{}\t", self.0)?;
write!(f, "{} \t", self.0)?;
if self.1.len() > 0 {
write!(f, "{}", self.1[0])?;
}
@ -304,6 +308,7 @@ impl TryFrom<&[u8]> for Program {
}
cur += 1;
while cur < value.len() {
let instruction: Instruction = value[cur..].try_into()?;
cur += instruction.byte_length() as usize;
prog.push(instruction);
@ -318,9 +323,13 @@ impl TryFrom<&[u8]> for Program {
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())
let mut res: Vec<u8> = vec![DeserializerControlCode::DataChunk as u8];
for dat in self.0 {
res.append(&mut dat.into());
}
res.push(DeserializerControlCode::CodeChunk as u8);
for instr in self.1 {
res.append(&mut instr.into());
}
res
}
@ -337,12 +346,12 @@ impl Display for Program {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), E> {
write!(f, "DATA:\n")?;
for i in self.0.iter() {
write!(f, " {}", i)?;
write!(f, " {}\n", i)?;
}
write!(f, "\nCODE:\n")?;
write!(f, "CODE:\n")?;
for i in self.1.iter() {
write!(f, " {}", i)?;
write!(f, " {}\n", i)?;
}
Ok(())
@ -354,7 +363,10 @@ impl FromStr for Program {
fn from_str(val: &str) -> Result<Self, Self::Err> {
//let mut datum = vec![];
let mut instrs = vec![];
let lines: Vec<&str> = val.split('\n').collect();
let lines: Vec<&str> = val
.split('\n')
.filter(|x| x.len() > 0)
.collect();
let mut cur = 0;
let mut toggle = 0;
@ -368,7 +380,7 @@ impl FromStr for Program {
match lines[cur] {
"DATA:" => return Err("datum parser unimplemented"),
"CODE:" => toggle = 1,
a => return Err("unknown section in document: "),
_ => return Err("unknown section in document: "),
}
}