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: "),
}
}