From ad39e269456a1a4933adffc3daff7afc3d31d365 Mon Sep 17 00:00:00 2001 From: Ava Affine Date: Mon, 15 Dec 2025 18:28:28 +0000 Subject: [PATCH] Hyphae: add assembler and disassembler 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 --- Cargo.lock | 13 ++++--- hyphae/Cargo.toml | 19 ++++++++++ hyphae/src/bin/hphc.rs | 77 +++++++++++++++++++++++++++++++++++++++ hyphae/src/bin/hphdump.rs | 52 ++++++++++++++++++++++++++ hyphae/src/serializer.rs | 48 +++++++++++++++--------- 5 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 hyphae/src/bin/hphc.rs create mode 100644 hyphae/src/bin/hphdump.rs diff --git a/Cargo.lock b/Cargo.lock index 35bd457..f9dd845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/hyphae/Cargo.toml b/hyphae/Cargo.toml index 06538cd..4d8fe1b 100644 --- a/hyphae/Cargo.toml +++ b/hyphae/Cargo.toml @@ -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"] } diff --git a/hyphae/src/bin/hphc.rs b/hyphae/src/bin/hphc.rs new file mode 100644 index 0000000..6e5f35a --- /dev/null +++ b/hyphae/src/bin/hphc.rs @@ -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 . + */ + +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, +} + +fn main() -> Result<(), Box> { + // 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 = program.into(); + let mut out_file = File::create(outfile)?; + out_file.write_all(&bytecode)?; + + Ok(()) +} diff --git a/hyphae/src/bin/hphdump.rs b/hyphae/src/bin/hphdump.rs new file mode 100644 index 0000000..d083a9f --- /dev/null +++ b/hyphae/src/bin/hphdump.rs @@ -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 . + */ + +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> { + // 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(()) +} diff --git a/hyphae/src/serializer.rs b/hyphae/src/serializer.rs index e0b990c..91186be 100644 --- a/hyphae/src/serializer.rs +++ b/hyphae/src/serializer.rs @@ -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[1..].parse::().is_ok() => + a if a.len() > 1 && + a.chars().nth(0).unwrap() == '%' && + a[1..].parse::().is_ok() => Ok(Operand(Address::Stack, a[1..].parse::().unwrap())), - a if a.chars().nth(0).unwrap() == '@' && - a.len() > 1 && - a[1..].parse::().is_ok() => + a if a.len() > 1 && + a.chars().nth(0).unwrap() == '@' && + a[1..].parse::().is_ok() => Ok(Operand(Address::Instr, a[1..].parse::().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::().is_ok() => @@ -232,7 +232,11 @@ impl Into> for Instruction { impl FromStr for Instruction { type Err = &'static str; fn from_str(v: &str) -> Result { - 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> for Program { fn into(self) -> Vec { - let mut res: Vec = vec![]; - for instr in self.0 { - res.append(&mut instr.into()) + let mut res: Vec = 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 { //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: "), } }