- updated common with new compiler/builder trait to provide a common

interface for build tools
- updated editor and build tooling to use new system
This commit is contained in:
2026-02-22 21:43:22 +00:00
parent 4ed5da259e
commit 7117b927f3
9 changed files with 203 additions and 129 deletions
+40 -27
View File
@@ -10,7 +10,10 @@ use std::{
}; };
pub use common::logging::log; pub use common::logging::log;
use common::prelude::Instruction; use common::{
build::{BuildError, Builder},
prelude::Instruction,
};
// Module declarations // Module declarations
#[macro_use] #[macro_use]
@@ -37,17 +40,27 @@ pub use self::{
use crate::util::logging::{Entry, Logger}; use crate::util::logging::{Entry, Logger};
pub struct CompilerEngine { pub struct Assembler {
result_tx: mpsc::Sender<Result<Vec<Instruction>, AssembleError>>, src_path: PathBuf,
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, AssembleError>>>, result_tx: mpsc::Sender<Result<Vec<u8>, AssembleError>>,
result_rx: Option<mpsc::Receiver<Result<Vec<u8>, AssembleError>>>,
is_running: bool, is_running: bool,
} }
impl CompilerEngine { impl From<AssembleError> for BuildError {
fn from(err: AssembleError) -> Self {
Self::Generic(err.to_string())
}
}
impl Builder for Assembler {
type Output = Vec<u8>;
#[must_use] #[must_use]
pub fn new() -> Self { fn new(src_path: impl Into<PathBuf>) -> Self {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
Self { Self {
src_path: src_path.into(),
result_tx: tx, result_tx: tx,
result_rx: Some(rx), result_rx: Some(rx),
is_running: false, is_running: false,
@@ -55,25 +68,29 @@ impl CompilerEngine {
} }
/// Start the compilation process in a separate thread /// Start the compilation process in a separate thread
pub fn start_compilation(&mut self, src: &Path) { fn start(&mut self) {
if self.is_running { if self.is_running {
return; return;
} }
let src = src.to_path_buf(); let src = self.src_path.clone();
let tx = self.result_tx.clone(); let tx = self.result_tx.clone();
thread::spawn(move || { thread::spawn(move || {
let result = assemble(&src); if let Ok(res) = assemble(&src) {
tx.send(result) let buffer: Vec<u8> = res
.iter()
.flat_map(|instruction| instruction.encode().to_be_bytes())
.collect();
tx.send(Ok(buffer))
.expect("Failed to send compilation result from worker thread"); .expect("Failed to send compilation result from worker thread");
}
}); });
self.is_running = true; self.is_running = true;
} }
/// Check if compilation is complete and get the result fn poll(&mut self) -> Option<Result<Self::Output, common::build::BuildError>> {
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, AssembleError>> {
if !self.is_running { if !self.is_running {
return None; return None;
} }
@@ -86,22 +103,20 @@ impl CompilerEngine {
{ {
Ok(result) => { Ok(result) => {
self.is_running = false; self.is_running = false;
Some(result) Some(result.map_err(std::convert::Into::into))
} }
Err(mpsc::TryRecvError::Empty) => None, Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => { Err(mpsc::TryRecvError::Disconnected) => {
self.is_running = false; 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 /// Block until compilation is complete and return the result
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, AssembleError> { fn output(&mut self) -> Result<Self::Output, common::build::BuildError> {
if !self.is_running {
return Err(AssembleError::Generic);
}
if let Ok(result) = self if let Ok(result) = self
.result_rx .result_rx
.take() .take()
@@ -109,14 +124,18 @@ impl CompilerEngine {
.recv() .recv()
{ {
self.is_running = false; self.is_running = false;
result result.map_err(std::convert::Into::into)
} else { } else {
self.is_running = false; 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<Vec<Instruction>, AssembleError> { fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
let mut modules = HashSet::new(); let mut modules = HashSet::new();
let mut program = Program::new(); let mut program = Program::new();
@@ -142,12 +161,6 @@ fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
Ok(instructions) Ok(instructions)
} }
impl Default for CompilerEngine {
fn default() -> Self {
Self::new()
}
}
fn prepare_dependency( fn prepare_dependency(
path: &Path, path: &Path,
modules: &mut HashSet<u64>, modules: &mut HashSet<u64>,
+1 -26
View File
@@ -18,33 +18,8 @@ pub mod tooling;
mod util; mod util;
pub mod prelude { pub mod prelude {
pub use crate::assembler::CompilerEngine; pub use crate::assembler::Assembler;
pub use crate::image_builder; pub use crate::image_builder;
pub use crate::tooling::brainf; pub use crate::tooling::brainf;
pub use crate::tooling::project; 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<u8> = 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(())
}
+10 -5
View File
@@ -1,9 +1,6 @@
use common as _; use common::{self as _, build::Builder};
use num_cpus as _;
use threadpool as _;
use assembler::{ use assembler::{
assemble_file,
prelude::*, prelude::*,
tooling::{brainf, project}, tooling::{brainf, project},
}; };
@@ -47,5 +44,13 @@ fn main() {
let input_path = &args[2]; let input_path = &args[2];
let output_path = &args[4]; 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);
}
} }
+52
View File
@@ -0,0 +1,52 @@
use std::{
fmt,
path::{Path, PathBuf},
};
#[derive(Debug, Clone)]
pub enum BuildError {
IoError(String),
Generic(String),
}
impl From<std::io::Error> for BuildError {
fn from(err: std::io::Error) -> Self {
Self::IoError(err.to_string())
}
}
impl From<Box<dyn std::error::Error>> for BuildError {
fn from(err: Box<dyn std::error::Error>) -> 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<PathBuf>) -> Self;
// starts compilation
fn start(&mut self);
// non-blocking function, returns output if completed
fn poll(&mut self) -> Option<Result<Self::Output, BuildError>>;
// blocking function, returns output when completed.
fn output(&mut self) -> Result<Self::Output, BuildError>;
fn write_result(&mut self, path: impl AsRef<Path>) -> Result<(), BuildError> {
let output = self.output()?;
std::fs::write(path.as_ref(), output)
.map_err(|e| BuildError::IoError(e.to_string()))
}
}
+1
View File
@@ -12,6 +12,7 @@
clippy::match_wildcard_for_single_variants clippy::match_wildcard_for_single_variants
)] )]
pub mod build;
pub mod instructions; pub mod instructions;
pub mod logging; pub mod logging;
+47 -37
View File
@@ -1,8 +1,11 @@
#![feature(try_trait_v2)] #![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}; use crate::{model::CompilerError, specialised::build_specialised};
@@ -11,33 +14,25 @@ mod frontend;
mod model; mod model;
mod specialised; mod specialised;
pub fn compile_file( pub struct Compiler {
input_path: &Path, src_path: PathBuf,
output_path: &Path, result: Option<Result<String, BuildError>>,
) -> Result<(), Box<dyn std::error::Error>> { }
let input = std::fs::read_to_string(input_path).expect("Failed to read input file");
let input_ext = input_path impl Compiler {
fn build(&mut self) -> Result<String, Box<dyn std::error::Error>> {
let input =
std::fs::read_to_string(&self.src_path).expect("Failed to read input file");
let input_ext = self
.src_path
.extension() .extension()
.and_then(|s| s.to_str()) .and_then(|s| s.to_str())
.unwrap_or(""); .unwrap_or("");
// check if we're using a specialised compiler // check if we're using a specialised compiler
if let Some(output) = build_specialised(input_ext, &input) { if let Some(output) = build_specialised(input_ext, &input) {
let result = match output { return output.map_err(|err| format!("Compilation failed: {err:?}").into());
Ok(output) => output,
Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
};
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(),
));
return Ok(());
} }
// Parse the input using the frontend, providing the file extension and data. // Parse the input using the frontend, providing the file extension and data.
@@ -46,29 +41,44 @@ pub fn compile_file(
Err(err) => return Err(format!("Compilation failed: {err:?}").into()), Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
}; };
println!("Parsed AST: {:#?}", ast); // println!("Parsed AST: {:#?}", ast);
let output_ext = output_path
.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
// Generate the output using the backend with the parsed result. // Generate the output using the backend with the parsed result.
let result = match backend::compiler_backend(output_ext, &ast) { let result = match backend::compiler_backend("dsa", &ast) {
Ok(result) => result, Ok(result) => result,
Err(err) => return Err(format!("Compilation failed: {err:?}").into()), Err(err) => return Err(format!("Compilation failed: {err:?}").into()),
}; };
// println!("{result}"); Ok(result)
std::fs::write(output_path, &result).expect("Failed to write output"); }
}
log(&format!( impl Builder for Compiler {
"Compilation Successful ✅ \n\tSource: {}\n\tOutput: {}\n", type Output = String;
input_path.display(),
output_path.display(),
));
Ok(()) fn new(src_path: impl Into<PathBuf>) -> Self {
Self {
src_path: src_path.into(),
result: None,
}
}
fn start(&mut self) {
match self.build() {
Ok(x) => self.result = Some(Ok(x)),
Err(err) => self.result = Some(Err(err.into())),
}
}
fn poll(&mut self) -> Option<Result<Self::Output, BuildError>> {
self.result.take()
}
fn output(&mut self) -> Result<Self::Output, BuildError> {
self.result.clone().ok_or(BuildError::Generic(String::from(
"Compiler was never started",
)))?
}
} }
pub fn error(msg: impl Into<String>) -> CompilerError { pub fn error(msg: impl Into<String>) -> CompilerError {
+16 -2
View File
@@ -1,4 +1,7 @@
use std::path::Path; use std::path::{Path, PathBuf};
use common::{build::Builder, logging::log};
use compiler::Compiler;
fn main() { fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]" // read from input file: syntax "c_compiler <src.c> [output.dsa]"
@@ -15,5 +18,16 @@ fn main() {
"output.dsa" "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,
));
}
} }
+8
View File
@@ -1,5 +1,7 @@
use core::fmt; use core::fmt;
use common::build::BuildError;
#[allow(unused)] #[allow(unused)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum CompilerError { pub enum CompilerError {
@@ -14,6 +16,12 @@ pub enum CompilerError {
Unimplemented(String), Unimplemented(String),
} }
impl From<CompilerError> for BuildError {
fn from(err: CompilerError) -> Self {
BuildError::Generic(format!("{:?}", err))
}
}
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Name { pub struct Name {
pub name: String, pub name: String,
+15 -19
View File
@@ -5,7 +5,9 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use common::build::Builder;
use common::prelude::Instruction; use common::prelude::Instruction;
use compiler::Compiler;
use egui::{Align, Context, Key, Layout, Ui}; use egui::{Align, Context, Key, Layout, Ui};
use dsa_editor::{CodeEditor, ColorTheme, Syntax}; use dsa_editor::{CodeEditor, ColorTheme, Syntax};
@@ -423,45 +425,39 @@ impl Editor {
if let Some(path) = &self.path { if let Some(path) = &self.path {
match path.extension().and_then(|ext| ext.to_str()) { match path.extension().and_then(|ext| ext.to_str()) {
Some("dsa") => { Some("dsa") => {
let mut compiler = CompilerEngine::new(); let mut assembler = Assembler::new(path);
compiler.start_compilation(path); assembler.start();
// Or block until done // Or block until done
let instructions = match compiler.wait_for_result() { self.output = match assembler.output() {
Ok(instructions) => instructions, Ok(instructions) => instructions,
Err(e) => { Err(e) => {
self.error = Some(e.to_string()); self.error = Some(e.to_string());
return; return;
} }
}; };
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
} }
Some("dsc") => { Some("dsc") => {
let output_path = Path::new(path).with_extension("dsa"); let dsa_path = Path::new(path).with_extension("dsa");
if let Err(e) = compiler::compile_file(path, &output_path) { let mut compiler = Compiler::new(path);
self.error = Some(format!("Compiler error: {e}")); compiler.start();
if let Err(e) = compiler.write_result(&dsa_path) {
self.error = Some(e.to_string());
return;
} }
let mut compiler = CompilerEngine::new(); let mut assembler = Assembler::new(&dsa_path);
compiler.start_compilation(&output_path); compiler.start();
// Or block until done // Or block until done
let instructions = match compiler.wait_for_result() { self.output = match assembler.output() {
Ok(instructions) => instructions, Ok(instructions) => instructions,
Err(e) => { Err(e) => {
self.error = Some(format!("Assembler error: {e}")); self.error = Some(format!("Assembler error: {e}"));
return; return;
} }
}; };
self.output = instructions
.iter()
.flat_map(|i| i.encode().to_be_bytes().to_vec())
.collect();
} }
Some("dsb") => { Some("dsb") => {
if let Ok(bytes) = fs::read(path) { if let Ok(bytes) = fs::read(path) {