assembler: broke everything, currently modularising
This commit is contained in:
Generated
+1
@@ -270,6 +270,7 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"threadpool",
|
"threadpool",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ clap = { version = "4.5.40", features = ["derive"] }
|
|||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
num_cpus = "1.17.0"
|
num_cpus = "1.17.0"
|
||||||
threadpool = "1.8.1"
|
threadpool = "1.8.1"
|
||||||
|
uuid = { version = "1.17.0", features = ["v4"] }
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ use std::{
|
|||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::assembler::{AssembleError, Token, expand_pseudo_ops, lexer, quick_hash};
|
use crate::assembler::{Node, Parser, ProgramRef, Task, resolve_dependencies};
|
||||||
use crate::assembler::{Node, Parser, resolve_dependencies};
|
use crate::assembler::{
|
||||||
|
Token, error::AssembleError, expand_pseudo_ops, lexer, quick_hash,
|
||||||
|
};
|
||||||
use crate::util::logging::Logger;
|
use crate::util::logging::Logger;
|
||||||
|
|
||||||
// pub fn new_assemble(path: &Path) {
|
// pub fn new_assemble(path: &Path) {
|
||||||
@@ -55,71 +57,6 @@ impl Default for Program {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProgramRef {
|
|
||||||
program: Arc<Mutex<Program>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgramRef {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(program: Program) -> Self {
|
|
||||||
Self {
|
|
||||||
program: Arc::new(Mutex::new(program)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(&self, path: &Path) {
|
|
||||||
self.program
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire program lock")
|
|
||||||
.registry
|
|
||||||
.insert(quick_hash(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_registered(&self, path: &Path) -> bool {
|
|
||||||
self.program
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire program lock")
|
|
||||||
.registry
|
|
||||||
.contains(&quick_hash(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn get_tasks(&self) -> Vec<&Task> {
|
|
||||||
// self.program.lock().unwrap().tasks.iter().collect()
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn add_task(&self, task: Task) {
|
|
||||||
self.program
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire program lock")
|
|
||||||
.add_task(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_module(&self, module: Module) {
|
|
||||||
self.program
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire program lock")
|
|
||||||
.modules
|
|
||||||
.push(module);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log(&self, message: &str) {
|
|
||||||
self.program
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to acquire program lock")
|
|
||||||
.logger
|
|
||||||
.log(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for ProgramRef {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
program: self.program.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub hash: u64,
|
pub hash: u64,
|
||||||
@@ -144,7 +81,8 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(path: PathBuf, program: ProgramRef) -> Result<Task, AssembleError> {
|
pub fn build(path: PathBuf, program: ProgramRef) -> Result<Task, AssembleError> {
|
||||||
// Spawn a thread that creates the main function and executes the lexer and parser.
|
// Spawn a thread that creates the main function and executes the lexer and
|
||||||
|
// parser.
|
||||||
let handle = thread::spawn(move || {
|
let handle = thread::spawn(move || {
|
||||||
let mut module =
|
let mut module =
|
||||||
Self::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
|
Self::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
|
||||||
@@ -154,7 +92,8 @@ impl Module {
|
|||||||
module.parse(tokens);
|
module.parse(tokens);
|
||||||
module.expand();
|
module.expand();
|
||||||
module.prepare_dependencies();
|
module.prepare_dependencies();
|
||||||
module
|
|
||||||
|
Ok(module)
|
||||||
}
|
}
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@@ -162,13 +101,12 @@ impl Module {
|
|||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Find a way to make this work without panicking.
|
Err(why)
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Task { module: handle })
|
Ok(Task::new(path, program)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex(&self) -> Result<Vec<Token>, AssembleError> {
|
fn lex(&self) -> Result<Vec<Token>, AssembleError> {
|
||||||
@@ -181,8 +119,13 @@ impl Module {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let src = fs::read_to_string(&self.path)
|
let src = fs::read_to_string(&self.path).map_err(|e| {
|
||||||
.map_err(|_| AssembleError::InvalidFile(self.path.clone()))?;
|
AssembleError::Io(format!(
|
||||||
|
"Failed to read file '{}': {}",
|
||||||
|
self.path.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
let file_hash = quick_hash(&self.path);
|
let file_hash = quick_hash(&self.path);
|
||||||
|
|
||||||
@@ -258,7 +201,3 @@ impl Module {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Task {
|
|
||||||
module: JoinHandle<Module>,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
//! Compiler engine for orchestrating the assembly process.
|
||||||
|
|
||||||
|
use crate::assembler::{AssembleError, Program, Task};
|
||||||
|
use common::prelude::Instruction;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Supported output formats for the assembler.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum OutputFormat {
|
||||||
|
/// Flat binary executable
|
||||||
|
Binary,
|
||||||
|
/// ELF relocatable object file
|
||||||
|
ElfObject,
|
||||||
|
/// ELF executable
|
||||||
|
ElfExecutable,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main compilation orchestrator that manages the assembly process.
|
||||||
|
pub struct CompilerEngine {
|
||||||
|
/// Configuration options for compilation
|
||||||
|
pub output_format: OutputFormat,
|
||||||
|
pub include_debug_info: bool,
|
||||||
|
pub optimization_level: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompilerEngine {
|
||||||
|
/// Creates a new compiler engine with default settings.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
output_format: OutputFormat::Binary,
|
||||||
|
include_debug_info: false,
|
||||||
|
optimization_level: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new compiler engine with specified output format.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_output_format(output_format: OutputFormat) -> Self {
|
||||||
|
Self {
|
||||||
|
output_format,
|
||||||
|
include_debug_info: false,
|
||||||
|
optimization_level: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the output format for compilation.
|
||||||
|
pub fn set_output_format(&mut self, format: OutputFormat) {
|
||||||
|
self.output_format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables or disables debug information generation.
|
||||||
|
pub fn set_debug_info(&mut self, enabled: bool) {
|
||||||
|
self.include_debug_info = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the optimization level (0-3).
|
||||||
|
pub fn set_optimization_level(&mut self, level: u8) {
|
||||||
|
self.optimization_level = level.min(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main assembly function that orchestrates the entire compilation process.
|
||||||
|
pub fn assemble(
|
||||||
|
&self,
|
||||||
|
main_path: &Path,
|
||||||
|
output_path: Option<&Path>,
|
||||||
|
) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let program = Program::new();
|
||||||
|
|
||||||
|
// Set the main path in the program
|
||||||
|
program.set_main_path(main_path.to_path_buf())?;
|
||||||
|
|
||||||
|
// Create and execute the main compilation task
|
||||||
|
let main_task = Task::new(main_path.to_path_buf(), program.clone())?;
|
||||||
|
let module = main_task.join()?;
|
||||||
|
|
||||||
|
program.add_module(module)?;
|
||||||
|
|
||||||
|
// Wait for all dependency compilation tasks to complete
|
||||||
|
self.wait_for_completion(&program)?;
|
||||||
|
|
||||||
|
// Generate final instructions
|
||||||
|
let instructions = self.generate_instructions(&program)?;
|
||||||
|
|
||||||
|
Ok(instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for all compilation tasks to complete.
|
||||||
|
fn wait_for_completion(&self, program: &Program) -> Result<(), AssembleError> {
|
||||||
|
let tasks = program.get_tasks()?;
|
||||||
|
|
||||||
|
for task in tasks {
|
||||||
|
let module = task.join()?;
|
||||||
|
program.add_module(module)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the final instruction stream from all compiled modules.
|
||||||
|
fn generate_instructions(
|
||||||
|
&self,
|
||||||
|
program: &Program,
|
||||||
|
) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let mut all_nodes = Vec::new();
|
||||||
|
|
||||||
|
// Collect all nodes from all modules
|
||||||
|
for module in program.get_modules()? {
|
||||||
|
all_nodes.extend(module.nodes.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply resolution and code generation
|
||||||
|
crate::assembler::create_sections(&mut all_nodes)?;
|
||||||
|
crate::assembler::resolve_symbols(&mut all_nodes)?;
|
||||||
|
crate::assembler::codegen(all_nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the default output path based on input path and output format.
|
||||||
|
fn default_output_path(&self, input_path: &Path) -> PathBuf {
|
||||||
|
let stem = input_path.file_stem().unwrap_or_default();
|
||||||
|
let parent = input_path.parent().unwrap_or(Path::new("."));
|
||||||
|
|
||||||
|
let extension = match self.output_format {
|
||||||
|
OutputFormat::Binary => "bin",
|
||||||
|
OutputFormat::ElfObject => "o",
|
||||||
|
OutputFormat::ElfExecutable => "elf",
|
||||||
|
};
|
||||||
|
|
||||||
|
parent.join(format!("{}.{}", stem.to_string_lossy(), extension))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CompilerEngine {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for simple assembly with default settings.
|
||||||
|
pub fn assemble(input_path: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let engine = CompilerEngine::new();
|
||||||
|
engine.assemble(input_path, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for assembling to ELF object format.
|
||||||
|
pub fn assemble_to_object(
|
||||||
|
input_path: &Path,
|
||||||
|
output_path: Option<&Path>,
|
||||||
|
) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let engine = CompilerEngine::with_output_format(OutputFormat::ElfObject);
|
||||||
|
engine.assemble(input_path, output_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for assembling to ELF executable format.
|
||||||
|
pub fn assemble_to_executable(
|
||||||
|
input_path: &Path,
|
||||||
|
output_path: Option<&Path>,
|
||||||
|
) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
|
let engine = CompilerEngine::with_output_format(OutputFormat::ElfExecutable);
|
||||||
|
engine.assemble(input_path, output_path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
//! Error types for the DSA assembler.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Comprehensive error type for assembly operations.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AssembleError {
|
||||||
|
/// IO-related errors (file not found, permission denied, etc.).
|
||||||
|
Io(std::io::Error),
|
||||||
|
|
||||||
|
/// Lexical analysis errors
|
||||||
|
Lexer {
|
||||||
|
message: String,
|
||||||
|
line: usize,
|
||||||
|
column: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Parsing errors
|
||||||
|
Parser {
|
||||||
|
message: String,
|
||||||
|
line: usize,
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Symbol resolution errors
|
||||||
|
Symbol {
|
||||||
|
message: String,
|
||||||
|
symbol_name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Code generation errors
|
||||||
|
Codegen {
|
||||||
|
message: String,
|
||||||
|
instruction: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Dependency resolution errors
|
||||||
|
Dependency {
|
||||||
|
message: String,
|
||||||
|
module_path: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Threading and synchronization errors
|
||||||
|
Threading(String),
|
||||||
|
|
||||||
|
/// Output generation errors
|
||||||
|
Output { message: String, format: String },
|
||||||
|
|
||||||
|
/// Generic assembly error
|
||||||
|
Generic(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AssembleError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Io(msg) => write!(f, "IO Error: {}", msg),
|
||||||
|
Self::Lexer {
|
||||||
|
message,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
} => {
|
||||||
|
write!(f, "Lexer Error at {}:{}: {}", line, column, message)
|
||||||
|
}
|
||||||
|
Self::Parser {
|
||||||
|
message,
|
||||||
|
line,
|
||||||
|
token,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Parser Error at line {}, token '{}': {}",
|
||||||
|
line, token, message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Self::Symbol {
|
||||||
|
message,
|
||||||
|
symbol_name,
|
||||||
|
} => {
|
||||||
|
write!(f, "Symbol Error '{}': {}", symbol_name, message)
|
||||||
|
}
|
||||||
|
Self::Codegen {
|
||||||
|
message,
|
||||||
|
instruction,
|
||||||
|
} => {
|
||||||
|
write!(f, "Codegen Error in '{}': {}", instruction, message)
|
||||||
|
}
|
||||||
|
Self::Dependency {
|
||||||
|
message,
|
||||||
|
module_path,
|
||||||
|
} => {
|
||||||
|
write!(f, "Dependency Error in '{}': {}", module_path, message)
|
||||||
|
}
|
||||||
|
Self::Threading(msg) => write!(f, "Threading Error: {}", msg),
|
||||||
|
Self::Output { message, format } => {
|
||||||
|
write!(f, "Output Error ({}): {}", format, message)
|
||||||
|
}
|
||||||
|
Self::Generic(msg) => write!(f, "Assembly Error: {}", msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for AssembleError {}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for AssembleError {
|
||||||
|
fn from(error: std::io::Error) -> Self {
|
||||||
|
Self::Io(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<std::sync::PoisonError<T>> for AssembleError {
|
||||||
|
fn from(error: std::sync::PoisonError<T>) -> Self {
|
||||||
|
Self::Threading(format!("Mutex poisoned: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-243
@@ -1,266 +1,43 @@
|
|||||||
#![allow(dead_code, unused)]
|
//! DSA Assembler module - converts assembly source code into executable instructions.
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::HashSet,
|
|
||||||
fmt, fs,
|
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::{Arc, Mutex, mpsc},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use common::prelude::Instruction;
|
use common::prelude::Instruction;
|
||||||
|
use std::path::Path;
|
||||||
// TODO: Use an actual logging or tracing library for pretty (scoped) output.
|
|
||||||
fn log(message: &str) {
|
|
||||||
println!("\x1b[32mINFO:\x1b[0m {message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module declarations
|
// Module declarations
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
|
||||||
#[allow(clippy::module_inception)]
|
|
||||||
pub mod assembler;
|
|
||||||
pub mod codegen;
|
pub mod codegen;
|
||||||
|
pub mod engine;
|
||||||
|
pub mod error;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
pub mod program;
|
||||||
pub mod resolver;
|
pub mod resolver;
|
||||||
|
pub mod task;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
// Re-exports
|
// Re-exports for backward compatibility and convenience
|
||||||
pub use self::{
|
pub use self::{
|
||||||
codegen::codegen,
|
codegen::codegen,
|
||||||
|
engine::{
|
||||||
|
CompilerEngine, OutputFormat, assemble, assemble_to_executable,
|
||||||
|
assemble_to_object,
|
||||||
|
},
|
||||||
|
error::AssembleError,
|
||||||
expand::expand_pseudo_ops,
|
expand::expand_pseudo_ops,
|
||||||
lexer::lexer,
|
lexer::lexer,
|
||||||
model::{Module, Node, Opcode, Symbol, Token, TokenType},
|
model::{Module, Node, Opcode, Symbol, Token, TokenType},
|
||||||
parser::{Parser, Program},
|
parser::Parser,
|
||||||
|
program::Program,
|
||||||
resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
resolver::{create_sections, resolve_dependencies, resolve_symbols},
|
||||||
|
task::Task,
|
||||||
|
util::{log, quick_hash},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::logging::{Entry, Logger};
|
/// The old assemble function for compatibility reasons.
|
||||||
|
pub fn legacy_assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||||
pub struct CompilerEngine {
|
engine::assemble(src)
|
||||||
result_tx: mpsc::Sender<Result<Vec<Instruction>, AssembleError>>,
|
|
||||||
result_rx: Option<mpsc::Receiver<Result<Vec<Instruction>, AssembleError>>>,
|
|
||||||
is_running: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompilerEngine {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
Self {
|
|
||||||
result_tx: tx,
|
|
||||||
result_rx: Some(rx),
|
|
||||||
is_running: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start the compilation process in a separate thread
|
|
||||||
pub fn start_compilation(&mut self, src: &Path) {
|
|
||||||
if self.is_running {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let src = src.to_path_buf();
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
|
|
||||||
self.is_running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if compilation is complete and get the result
|
|
||||||
pub fn try_get_result(&mut self) -> Option<Result<Vec<Instruction>, AssembleError>> {
|
|
||||||
if !self.is_running {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self
|
|
||||||
.result_rx
|
|
||||||
.as_ref()
|
|
||||||
.expect("result_rx should be Some while compilation is running")
|
|
||||||
.try_recv()
|
|
||||||
{
|
|
||||||
Ok(result) => {
|
|
||||||
self.is_running = false;
|
|
||||||
Some(result)
|
|
||||||
}
|
|
||||||
Err(mpsc::TryRecvError::Empty) => None,
|
|
||||||
Err(mpsc::TryRecvError::Disconnected) => {
|
|
||||||
self.is_running = false;
|
|
||||||
Some(Err(AssembleError::Generic))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Block until compilation is complete and return the result
|
|
||||||
pub fn wait_for_result(&mut self) -> Result<Vec<Instruction>, AssembleError> {
|
|
||||||
if !self.is_running {
|
|
||||||
return Err(AssembleError::Generic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(result) = self
|
|
||||||
.result_rx
|
|
||||||
.take()
|
|
||||||
.expect("result_rx should be Some while waiting for compilation result")
|
|
||||||
.recv()
|
|
||||||
{
|
|
||||||
self.is_running = false;
|
|
||||||
result
|
|
||||||
} else {
|
|
||||||
self.is_running = false;
|
|
||||||
Err(AssembleError::Generic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
|
||||||
let mut modules = HashSet::new();
|
|
||||||
let mut program = Program::new();
|
|
||||||
|
|
||||||
let hash = quick_hash(src);
|
|
||||||
|
|
||||||
if modules.contains(&hash) {
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepare_dependency(src, &mut modules, &mut program)?;
|
|
||||||
|
|
||||||
let mut nodes = program.nodes.clone();
|
|
||||||
|
|
||||||
create_sections(&mut nodes)?;
|
|
||||||
resolve_symbols(&mut nodes)?;
|
|
||||||
|
|
||||||
let instructions = codegen(nodes)?;
|
|
||||||
Ok(instructions)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CompilerEngine {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_dependency(
|
|
||||||
path: &Path,
|
|
||||||
modules: &mut HashSet<u64>,
|
|
||||||
program: &mut Program,
|
|
||||||
) -> Result<(), AssembleError> {
|
|
||||||
let filename = path
|
|
||||||
.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.expect("Failed to get file name from path");
|
|
||||||
|
|
||||||
if let Ok(path) = path.canonicalize() {
|
|
||||||
log(&format!(
|
|
||||||
"{:20} {:20} [{}]",
|
|
||||||
"Building",
|
|
||||||
filename,
|
|
||||||
path.display()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let src = fs::read_to_string(path)
|
|
||||||
.map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
|
|
||||||
let file_hash = quick_hash(path);
|
|
||||||
|
|
||||||
log(&format!("{:20} {:20}", "Tokenising", filename));
|
|
||||||
let tokens = lexer::lexer(src, file_hash)?;
|
|
||||||
|
|
||||||
log(&format!("{:20} {:20}", "Parsing", filename));
|
|
||||||
let parsed = Parser::parse_nodes(tokens)?;
|
|
||||||
|
|
||||||
log(&format!("{:20} {:20}", "Resolving Deps", filename));
|
|
||||||
// Get the parent directory of the source file to use as the base directory
|
|
||||||
let base_dir = path
|
|
||||||
.parent()
|
|
||||||
.ok_or_else(|| AssembleError::InvalidFile(path.to_path_buf()))?;
|
|
||||||
let mut nodes = expand_pseudo_ops(parsed, file_hash)?;
|
|
||||||
nodes = resolve_dependencies(nodes, base_dir)?;
|
|
||||||
|
|
||||||
let deps = Parser::get_dependencies(&nodes, path)?;
|
|
||||||
|
|
||||||
log(&format!(
|
|
||||||
"{:20} {:20}",
|
|
||||||
"Expanding PseudoInstructions", filename
|
|
||||||
));
|
|
||||||
|
|
||||||
// add a section instruction
|
|
||||||
nodes.insert(
|
|
||||||
0,
|
|
||||||
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
|
|
||||||
);
|
|
||||||
|
|
||||||
for n in &nodes {
|
|
||||||
println!("{n}");
|
|
||||||
}
|
|
||||||
|
|
||||||
program.add_module(nodes);
|
|
||||||
|
|
||||||
for dep in deps {
|
|
||||||
log(&format!(
|
|
||||||
"{:20} {:20}",
|
|
||||||
"Including",
|
|
||||||
dep.file_name()
|
|
||||||
.and_then(|f| f.to_str())
|
|
||||||
.expect("Dependency path has no file name or is not valid UTF-8")
|
|
||||||
));
|
|
||||||
|
|
||||||
let dep_hash = quick_hash(&dep);
|
|
||||||
if modules.insert(dep_hash) {
|
|
||||||
prepare_dependency(dep.as_path(), modules, program)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum AssembleError {
|
|
||||||
Generic,
|
|
||||||
UnexpectedEof,
|
|
||||||
InvalidFile(PathBuf),
|
|
||||||
UnexpectedToken(Token, TokenType),
|
|
||||||
InvalidArg,
|
|
||||||
UndefinedSymbol(Symbol),
|
|
||||||
/// Contains the nth element missing from the instruction.
|
|
||||||
MissingArgument(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for AssembleError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Generic => write!(f, "Generic error"),
|
|
||||||
Self::UnexpectedToken(tok, expected) => {
|
|
||||||
write!(f, "Unexpected token {tok:?}, expected {expected:?}")
|
|
||||||
}
|
|
||||||
Self::UnexpectedEof => write!(f, "Unexpected end of file"),
|
|
||||||
Self::InvalidFile(path) => write!(f, "Invalid file `{}`", path.display()),
|
|
||||||
Self::InvalidArg => write!(f, "Invalid argument"),
|
|
||||||
Self::UndefinedSymbol(symbol) => {
|
|
||||||
write!(f, "Undefined symbol {symbol}")
|
|
||||||
}
|
|
||||||
Self::MissingArgument(n) => {
|
|
||||||
write!(f, "Missing argument #{n} from instruction arguments.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quick_hash(value: &Path) -> u64 {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
value
|
|
||||||
.canonicalize()
|
|
||||||
.expect("Failed to canonicalize path for quick_hash")
|
|
||||||
.to_str()
|
|
||||||
.hash(&mut hasher);
|
|
||||||
|
|
||||||
hasher.finish()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
//! Data models for the DSA assembler.
|
||||||
|
|
||||||
|
use crate::assembler::{AssembleError, Parser, Program, expand_pseudo_ops, lexer};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use std::{fmt, str::FromStr};
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::assembler::AssembleError;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub symbol: Option<Symbol>,
|
pub symbol: Option<Symbol>,
|
||||||
pub opcode: Opcode,
|
pub opcode: Opcode,
|
||||||
@@ -40,7 +44,9 @@ impl Node {
|
|||||||
self.args()
|
self.args()
|
||||||
.get(index)
|
.get(index)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(AssembleError::InvalidArg)
|
// TODO: This is a bad place to throw an error unless we write code to attach
|
||||||
|
// context.
|
||||||
|
.ok_or(AssembleError::Generic("Invalid argument index".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,15 +73,6 @@ impl fmt::Display for Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Module {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Unresolved(name) => write!(f, "{name}"),
|
|
||||||
Self::Resolved(name) => write!(f, "{name}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Opcode {
|
impl fmt::Display for Opcode {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -160,12 +157,6 @@ impl PartialEq for Symbol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Module {
|
|
||||||
Resolved(u64),
|
|
||||||
Unresolved(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
Register(Register),
|
Register(Register),
|
||||||
@@ -196,7 +187,7 @@ impl TokenType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Opcode {
|
pub enum Opcode {
|
||||||
// Real instructions (0x00-0x26)
|
// Real instructions (0x00-0x26)
|
||||||
Nop,
|
Nop,
|
||||||
@@ -417,3 +408,125 @@ impl Opcode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a single source module and its compilation state.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct Module {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub hash: u64,
|
||||||
|
pub nodes: Vec<Node>,
|
||||||
|
program: Program,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Module {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Module {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Module {{ id: {}, path: {}, nodes: {} }}",
|
||||||
|
self.id,
|
||||||
|
self.path.display(),
|
||||||
|
self.nodes.len()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Module {}
|
||||||
|
|
||||||
|
impl Module {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(path: PathBuf, hash: u64, nodes: Vec<Node>, program: Program) -> Self {
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
path,
|
||||||
|
hash,
|
||||||
|
nodes,
|
||||||
|
program,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the full compilation pipeline for this module.
|
||||||
|
pub fn compile(&mut self) -> Result<(), AssembleError> {
|
||||||
|
self.lex()?;
|
||||||
|
self.parse()?;
|
||||||
|
self.expand()?;
|
||||||
|
self.prepare_dependencies()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lexical analysis stage.
|
||||||
|
pub fn lex(&mut self) -> Result<Vec<crate::assembler::Token>, AssembleError> {
|
||||||
|
// Log the build
|
||||||
|
if let Ok(path) = self.path.canonicalize() {
|
||||||
|
let _ = self.program.log(&format!(
|
||||||
|
"{:20} {:20} [{}]",
|
||||||
|
"Building",
|
||||||
|
self.get_filename(),
|
||||||
|
path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and lex the file
|
||||||
|
let source = std::fs::read_to_string(&self.path)?;
|
||||||
|
lexer(source, self.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsing stage.
|
||||||
|
pub fn parse(&mut self) -> Result<(), AssembleError> {
|
||||||
|
let source = std::fs::read_to_string(&self.path)?;
|
||||||
|
let tokens = lexer(source, self.hash)?;
|
||||||
|
let nodes = Parser::parse_nodes(tokens)?;
|
||||||
|
self.nodes = nodes;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pseudo-instruction expansion stage.
|
||||||
|
pub fn expand(&mut self) -> Result<(), AssembleError> {
|
||||||
|
self.nodes = expand_pseudo_ops(self.nodes.clone(), self.hash)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dependency resolution stage.
|
||||||
|
pub fn prepare_dependencies(&self) -> Result<(), AssembleError> {
|
||||||
|
// let base_dir = self.path.parent();
|
||||||
|
|
||||||
|
let dependencies = Parser::get_dependencies(&self.nodes, &self.path)?;
|
||||||
|
|
||||||
|
for dep in dependencies {
|
||||||
|
if self.program.is_registered(&dep)? {
|
||||||
|
// we have already built this module!
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.program.register(&dep)?;
|
||||||
|
|
||||||
|
// create new module task
|
||||||
|
match Task::new(dep, self.program.clone()) {
|
||||||
|
Ok(task) => {
|
||||||
|
if let Err(e) = self.program.add_task(task) {
|
||||||
|
eprintln!("Error adding task: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(why) => {
|
||||||
|
eprintln!("Error building program: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the filename from a [`PathBuf`].
|
||||||
|
fn get_filename(&self) -> &str {
|
||||||
|
self.path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::assembler::Task;
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
//! Program state management for multi-module compilation.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::assembler::{AssembleError, Module, Task, quick_hash};
|
||||||
|
use crate::util::logging::Logger;
|
||||||
|
|
||||||
|
/// Main program state containing all modules and compilation metadata.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Program {
|
||||||
|
/// A field to be passed into a hasher.
|
||||||
|
hash_me: Uuid,
|
||||||
|
inner: Arc<Mutex<ProgramInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for Program {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.hash_me.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Program {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
*self.inner.lock().unwrap() == *other.inner.lock().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct ProgramInner {
|
||||||
|
pub main_path: PathBuf,
|
||||||
|
pub registry: HashSet<u64>,
|
||||||
|
pub modules: Vec<Module>,
|
||||||
|
pub tasks: Vec<Task>,
|
||||||
|
pub logger: Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Program {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
hash_me: Uuid::new_v4(),
|
||||||
|
inner: Arc::new(Mutex::new(ProgramInner {
|
||||||
|
registry: HashSet::new(),
|
||||||
|
modules: Vec::new(),
|
||||||
|
tasks: Vec::new(),
|
||||||
|
main_path: PathBuf::new(),
|
||||||
|
logger: Logger::new(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a module path to prevent duplicate compilation.
|
||||||
|
pub fn register(&self, path: &std::path::Path) -> Result<(), AssembleError> {
|
||||||
|
self.inner.lock()?.registry.insert(quick_hash(path));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a module path is already registered.
|
||||||
|
pub fn is_registered(&self, path: &std::path::Path) -> Result<bool, AssembleError> {
|
||||||
|
Ok(self.inner.lock()?.registry.contains(&quick_hash(path)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all compilation tasks.
|
||||||
|
pub fn get_tasks(&self) -> Result<Vec<Task>, AssembleError> {
|
||||||
|
Ok(self.inner.lock()?.tasks.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new compilation task.
|
||||||
|
pub fn add_task(&self, task: Task) -> Result<(), AssembleError> {
|
||||||
|
self.inner.lock()?.tasks.push(task);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a compiled module to the program.
|
||||||
|
pub fn add_module(&self, module: Module) -> Result<(), AssembleError> {
|
||||||
|
self.inner.lock()?.modules.push(module);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all compiled modules.
|
||||||
|
pub fn get_modules(&self) -> Result<Vec<Module>, AssembleError> {
|
||||||
|
Ok(self.inner.lock()?.modules.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs a message using the program's logger.
|
||||||
|
pub fn log(&self, message: &str) -> Result<(), AssembleError> {
|
||||||
|
self.inner.lock()?.logger.log(message);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the main path for the program.
|
||||||
|
pub fn set_main_path(&self, path: PathBuf) -> Result<(), AssembleError> {
|
||||||
|
self.inner.lock()?.main_path = path;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the main path for the program.
|
||||||
|
pub fn get_main_path(&self) -> Result<PathBuf, AssembleError> {
|
||||||
|
Ok(self.inner.lock()?.main_path.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Program {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hash_me: self.hash_me.clone(),
|
||||||
|
inner: Arc::clone(&self.inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Program {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
//! Threading utilities for parallel module compilation.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
|
thread::{self, JoinHandle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::assembler::{AssembleError, Module, Program, quick_hash};
|
||||||
|
|
||||||
|
/// Represents a threaded compilation task for a single module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Task {
|
||||||
|
id: Uuid,
|
||||||
|
module_handle: Arc<JoinHandle<Result<Module, AssembleError>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Task {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
/// Creates a new compilation task for the given module path.
|
||||||
|
pub fn new(path: PathBuf, program: Program) -> Result<Self, AssembleError> {
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
let mut module =
|
||||||
|
Module::new(path.clone(), quick_hash(&path), Vec::new(), program.clone());
|
||||||
|
|
||||||
|
// Execute the compilation pipeline
|
||||||
|
match module.compile() {
|
||||||
|
Ok(()) => Ok(module),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Error building program at path `{}`: {}",
|
||||||
|
path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
module_handle: Arc::new(handle),
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a task from an existing join handle (for compatibility).
|
||||||
|
pub fn from_handle(handle: JoinHandle<Result<Module, AssembleError>>) -> Self {
|
||||||
|
Self {
|
||||||
|
module_handle: Arc::new(handle),
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for the compilation task to complete and returns the compiled module.
|
||||||
|
pub fn join(self) -> Result<Module, AssembleError> {
|
||||||
|
let Some(join_handle) = Arc::try_unwrap(self.module_handle).ok() else {
|
||||||
|
let err_msg = String::from(
|
||||||
|
"Cannot take ownership of reference counted task join_handle, multiple references exist.",
|
||||||
|
);
|
||||||
|
eprintln!("{err_msg}");
|
||||||
|
return Err(AssembleError::Threading(err_msg));
|
||||||
|
};
|
||||||
|
|
||||||
|
match join_handle.join() {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(panic_payload) => {
|
||||||
|
let err_msg = format!(
|
||||||
|
"Task thread panicked: {:?}",
|
||||||
|
panic_payload
|
||||||
|
.downcast_ref::<String>()
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.or_else(|| panic_payload.downcast_ref::<&str>().copied())
|
||||||
|
.unwrap_or("Unknown panic")
|
||||||
|
);
|
||||||
|
eprintln!("{err_msg}");
|
||||||
|
Err(AssembleError::Threading(err_msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Task {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
id: self.id.clone(),
|
||||||
|
module_handle: Arc::clone(&self.module_handle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//! Utility functions for the assembler.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Quick hash function for file paths.
|
||||||
|
pub fn quick_hash(value: &Path) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
value
|
||||||
|
.canonicalize()
|
||||||
|
.expect("Failed to canonicalize path for quick_hash")
|
||||||
|
.to_str()
|
||||||
|
.hash(&mut hasher);
|
||||||
|
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: Use an actual logging or tracing library for pretty (scoped) output.
|
||||||
|
pub fn log(message: &str) {
|
||||||
|
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use std::{fmt, sync::mpsc::Sender};
|
use std::{fmt, sync::mpsc::Sender};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Logger {}
|
pub struct Logger {}
|
||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub enum InstructionType {
|
|||||||
Immediate,
|
Immediate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Register {
|
pub enum Register {
|
||||||
// general purpose registers
|
// general purpose registers
|
||||||
|
|||||||
Reference in New Issue
Block a user