From 7117b927f33fb9c57f37480cab2a74f656eecbf5 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 22 Feb 2026 21:43:22 +0000 Subject: [PATCH] - updated common with new compiler/builder trait to provide a common interface for build tools - updated editor and build tooling to use new system --- assembler/src/assembler/mod.rs | 69 ++++++++++-------- assembler/src/lib.rs | 27 +------- assembler/src/main.rs | 15 ++-- common/src/build.rs | 52 ++++++++++++++ common/src/lib.rs | 1 + compiler/src/lib.rs | 108 ++++++++++++++++------------- compiler/src/main.rs | 18 ++++- compiler/src/model.rs | 8 +++ emulator/src/emulator/ui/editor.rs | 34 ++++----- 9 files changed, 203 insertions(+), 129 deletions(-) create mode 100644 common/src/build.rs diff --git a/assembler/src/assembler/mod.rs b/assembler/src/assembler/mod.rs index a9d83ad..862187a 100644 --- a/assembler/src/assembler/mod.rs +++ b/assembler/src/assembler/mod.rs @@ -10,7 +10,10 @@ use std::{ }; pub use common::logging::log; -use common::prelude::Instruction; +use common::{ + build::{BuildError, Builder}, + prelude::Instruction, +}; // Module declarations #[macro_use] @@ -37,17 +40,27 @@ pub use self::{ use crate::util::logging::{Entry, Logger}; -pub struct CompilerEngine { - result_tx: mpsc::Sender, AssembleError>>, - result_rx: Option, AssembleError>>>, +pub struct Assembler { + src_path: PathBuf, + result_tx: mpsc::Sender, AssembleError>>, + result_rx: Option, AssembleError>>>, is_running: bool, } -impl CompilerEngine { +impl From for BuildError { + fn from(err: AssembleError) -> Self { + Self::Generic(err.to_string()) + } +} + +impl Builder for Assembler { + type Output = Vec; + #[must_use] - pub fn new() -> Self { + fn new(src_path: impl Into) -> Self { let (tx, rx) = mpsc::channel(); Self { + src_path: src_path.into(), result_tx: tx, result_rx: Some(rx), is_running: false, @@ -55,25 +68,29 @@ impl CompilerEngine { } /// Start the compilation process in a separate thread - pub fn start_compilation(&mut self, src: &Path) { + fn start(&mut self) { if self.is_running { return; } - let src = src.to_path_buf(); + let src = self.src_path.clone(); let tx = self.result_tx.clone(); thread::spawn(move || { - let result = assemble(&src); - tx.send(result) - .expect("Failed to send compilation result from worker thread"); + if let Ok(res) = assemble(&src) { + let buffer: Vec = res + .iter() + .flat_map(|instruction| instruction.encode().to_be_bytes()) + .collect(); + tx.send(Ok(buffer)) + .expect("Failed to send compilation result from worker thread"); + } }); self.is_running = true; } - /// Check if compilation is complete and get the result - pub fn try_get_result(&mut self) -> Option, AssembleError>> { + fn poll(&mut self) -> Option> { if !self.is_running { return None; } @@ -86,22 +103,20 @@ impl CompilerEngine { { Ok(result) => { self.is_running = false; - Some(result) + Some(result.map_err(std::convert::Into::into)) } Err(mpsc::TryRecvError::Empty) => None, Err(mpsc::TryRecvError::Disconnected) => { self.is_running = false; - Some(Err(AssembleError::Generic)) + Some(Err(BuildError::Generic(String::from( + "Compilation terminated before a result was returned", + )))) } } } /// Block until compilation is complete and return the result - pub fn wait_for_result(&mut self) -> Result, AssembleError> { - if !self.is_running { - return Err(AssembleError::Generic); - } - + fn output(&mut self) -> Result { if let Ok(result) = self .result_rx .take() @@ -109,14 +124,18 @@ impl CompilerEngine { .recv() { self.is_running = false; - result + result.map_err(std::convert::Into::into) } else { self.is_running = false; - Err(AssembleError::Generic) + Err(BuildError::Generic(String::from( + "Compilation terminated before a result was returned", + ))) } } } +impl Assembler {} + fn assemble(src: &Path) -> Result, AssembleError> { let mut modules = HashSet::new(); let mut program = Program::new(); @@ -142,12 +161,6 @@ fn assemble(src: &Path) -> Result, AssembleError> { Ok(instructions) } -impl Default for CompilerEngine { - fn default() -> Self { - Self::new() - } -} - fn prepare_dependency( path: &Path, modules: &mut HashSet, diff --git a/assembler/src/lib.rs b/assembler/src/lib.rs index 5717e31..a689c6c 100644 --- a/assembler/src/lib.rs +++ b/assembler/src/lib.rs @@ -18,33 +18,8 @@ pub mod tooling; mod util; pub mod prelude { - pub use crate::assembler::CompilerEngine; + pub use crate::assembler::Assembler; pub use crate::image_builder; pub use crate::tooling::brainf; pub use crate::tooling::project; } - -use std::{fs, path::Path}; - -use num_cpus as _; -use threadpool as _; - -use crate::prelude::CompilerEngine; - -pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> { - let mut engine = CompilerEngine::new(); - engine.start_compilation(Path::new(input)); - let result = engine.wait_for_result().expect("assembler failed."); - - let buffer: Vec = result - .iter() - .flat_map(|instruction| instruction.encode().to_be_bytes()) - .collect(); - - if let Err(e) = fs::write(output, buffer) { - eprintln!("Failed to write to output file: {e}"); - std::process::exit(1); - } - - Ok(()) -} diff --git a/assembler/src/main.rs b/assembler/src/main.rs index 7b36083..0a73473 100644 --- a/assembler/src/main.rs +++ b/assembler/src/main.rs @@ -1,9 +1,6 @@ -use common as _; -use num_cpus as _; -use threadpool as _; +use common::{self as _, build::Builder}; use assembler::{ - assemble_file, prelude::*, tooling::{brainf, project}, }; @@ -47,5 +44,13 @@ fn main() { let input_path = &args[2]; let output_path = &args[4]; - assemble_file(input_path, output_path).unwrap(); + + let mut engine = Assembler::new(PathBuf::from(input_path)); + engine.start(); + let result = engine.output().expect("assembler failed."); + + if let Err(e) = fs::write(output_path, result) { + eprintln!("Failed to write to output file: {e}"); + std::process::exit(1); + } } diff --git a/common/src/build.rs b/common/src/build.rs new file mode 100644 index 0000000..c04a321 --- /dev/null +++ b/common/src/build.rs @@ -0,0 +1,52 @@ +use std::{ + fmt, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Clone)] +pub enum BuildError { + IoError(String), + Generic(String), +} + +impl From for BuildError { + fn from(err: std::io::Error) -> Self { + Self::IoError(err.to_string()) + } +} + +impl From> for BuildError { + fn from(err: Box) -> Self { + Self::Generic(err.to_string()) + } +} + +impl fmt::Display for BuildError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::IoError(err) => write!(f, "IO Error: {err}"), + Self::Generic(err) => write!(f, "Generic Error: {err}"), + } + } +} + +pub trait Builder { + type Output: Clone + std::convert::AsRef<[u8]>; + + fn new(src_path: impl Into) -> Self; + + // starts compilation + fn start(&mut self); + + // non-blocking function, returns output if completed + fn poll(&mut self) -> Option>; + + // blocking function, returns output when completed. + fn output(&mut self) -> Result; + + fn write_result(&mut self, path: impl AsRef) -> Result<(), BuildError> { + let output = self.output()?; + std::fs::write(path.as_ref(), output) + .map_err(|e| BuildError::IoError(e.to_string())) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 23c524e..8d05a7e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,6 +12,7 @@ clippy::match_wildcard_for_single_variants )] +pub mod build; pub mod instructions; pub mod logging; diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 460445f..fddb7da 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,8 +1,11 @@ #![feature(try_trait_v2)] -use std::path::Path; +use std::path::{Path, PathBuf}; -use common::logging::log; +use common::{ + build::{BuildError, Builder}, + logging::log, +}; use crate::{model::CompilerError, specialised::build_specialised}; @@ -11,64 +14,71 @@ mod frontend; mod model; mod specialised; -pub fn compile_file( - input_path: &Path, - output_path: &Path, -) -> Result<(), Box> { - let input = std::fs::read_to_string(input_path).expect("Failed to read input file"); +pub struct Compiler { + src_path: PathBuf, + result: Option>, +} - let input_ext = input_path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or(""); +impl Compiler { + fn build(&mut self) -> Result> { + let input = + std::fs::read_to_string(&self.src_path).expect("Failed to read input file"); - // check if we're using a specialised compiler - if let Some(output) = build_specialised(input_ext, &input) { - let result = match output { - Ok(output) => output, + let input_ext = self + .src_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or(""); + + // check if we're using a specialised compiler + if let Some(output) = build_specialised(input_ext, &input) { + return output.map_err(|err| format!("Compilation failed: {err:?}").into()); + } + + // Parse the input using the frontend, providing the file extension and data. + let ast = match frontend::compiler_frontend(input_ext, &input) { + Ok(ast) => ast, Err(err) => return Err(format!("Compilation failed: {err:?}").into()), }; - std::fs::write(output_path, &result).expect("Failed to write output"); + // println!("Parsed AST: {:#?}", ast); - log(&format!( - "Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", - input_path.display(), - output_path.display(), - )); + // Generate the output using the backend with the parsed result. + let result = match backend::compiler_backend("dsa", &ast) { + Ok(result) => result, + Err(err) => return Err(format!("Compilation failed: {err:?}").into()), + }; - return Ok(()); + Ok(result) + } +} + +impl Builder for Compiler { + type Output = String; + + fn new(src_path: impl Into) -> Self { + Self { + src_path: src_path.into(), + result: None, + } } - // Parse the input using the frontend, providing the file extension and data. - let ast = match frontend::compiler_frontend(input_ext, &input) { - Ok(ast) => ast, - Err(err) => return Err(format!("Compilation failed: {err:?}").into()), - }; + fn start(&mut self) { + match self.build() { + Ok(x) => self.result = Some(Ok(x)), + Err(err) => self.result = Some(Err(err.into())), + } + } - println!("Parsed AST: {:#?}", ast); + fn poll(&mut self) -> Option> { + self.result.take() + } - let output_ext = output_path - .extension() - .and_then(|s| s.to_str()) - .unwrap_or(""); - - // Generate the output using the backend with the parsed result. - let result = match backend::compiler_backend(output_ext, &ast) { - Ok(result) => result, - Err(err) => return Err(format!("Compilation failed: {err:?}").into()), - }; - - // println!("{result}"); - std::fs::write(output_path, &result).expect("Failed to write output"); - - log(&format!( - "Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", - input_path.display(), - output_path.display(), - )); - - Ok(()) + fn output(&mut self) -> Result { + self.result.clone().ok_or(BuildError::Generic(String::from( + "Compiler was never started", + )))? + } } pub fn error(msg: impl Into) -> CompilerError { diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 442c770..fa2bb43 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -1,4 +1,7 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; + +use common::{build::Builder, logging::log}; +use compiler::Compiler; fn main() { // read from input file: syntax "c_compiler [output.dsa]" @@ -15,5 +18,16 @@ fn main() { "output.dsa" }; - compiler::compile_file(Path::new(input_file), Path::new(output_file)).unwrap(); + { + let mut builder = Compiler::new(PathBuf::from(input_file)); + builder.start(); + let result = builder.output().unwrap(); + + std::fs::write(output_file, &result).expect("Failed to write output"); + + log(&format!( + "Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", + input_file, output_file, + )); + } } diff --git a/compiler/src/model.rs b/compiler/src/model.rs index fe6c118..546d442 100644 --- a/compiler/src/model.rs +++ b/compiler/src/model.rs @@ -1,5 +1,7 @@ use core::fmt; +use common::build::BuildError; + #[allow(unused)] #[derive(Debug, Clone)] pub enum CompilerError { @@ -14,6 +16,12 @@ pub enum CompilerError { Unimplemented(String), } +impl From for BuildError { + fn from(err: CompilerError) -> Self { + BuildError::Generic(format!("{:?}", err)) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct Name { pub name: String, diff --git a/emulator/src/emulator/ui/editor.rs b/emulator/src/emulator/ui/editor.rs index c2edfff..e70fc07 100644 --- a/emulator/src/emulator/ui/editor.rs +++ b/emulator/src/emulator/ui/editor.rs @@ -5,7 +5,9 @@ use std::{ path::{Path, PathBuf}, }; +use common::build::Builder; use common::prelude::Instruction; +use compiler::Compiler; use egui::{Align, Context, Key, Layout, Ui}; use dsa_editor::{CodeEditor, ColorTheme, Syntax}; @@ -423,45 +425,39 @@ impl Editor { if let Some(path) = &self.path { match path.extension().and_then(|ext| ext.to_str()) { Some("dsa") => { - let mut compiler = CompilerEngine::new(); - compiler.start_compilation(path); + let mut assembler = Assembler::new(path); + assembler.start(); // Or block until done - let instructions = match compiler.wait_for_result() { + self.output = match assembler.output() { Ok(instructions) => instructions, Err(e) => { self.error = Some(e.to_string()); return; } }; - - self.output = instructions - .iter() - .flat_map(|i| i.encode().to_be_bytes().to_vec()) - .collect(); } Some("dsc") => { - let output_path = Path::new(path).with_extension("dsa"); - if let Err(e) = compiler::compile_file(path, &output_path) { - self.error = Some(format!("Compiler error: {e}")); + let dsa_path = Path::new(path).with_extension("dsa"); + let mut compiler = Compiler::new(path); + compiler.start(); + + if let Err(e) = compiler.write_result(&dsa_path) { + self.error = Some(e.to_string()); + return; } - let mut compiler = CompilerEngine::new(); - compiler.start_compilation(&output_path); + let mut assembler = Assembler::new(&dsa_path); + compiler.start(); // Or block until done - let instructions = match compiler.wait_for_result() { + self.output = match assembler.output() { Ok(instructions) => instructions, Err(e) => { self.error = Some(format!("Assembler error: {e}")); return; } }; - - self.output = instructions - .iter() - .flat_map(|i| i.encode().to_be_bytes().to_vec()) - .collect(); } Some("dsb") => { if let Ok(bytes) = fs::read(path) {