diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-02-05 12:58:02 +1300 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-02-05 13:03:36 +1300 |
commit | 80da2af821385b2fc89091e9ac37a047349da4bd (patch) | |
tree | 2ba50368301e041f8d1b99145ab0a1fe28f91571 /src/errors | |
parent | 8d11be64f6c1747e7c4049105a6dd4ea9ab0d27f (diff) | |
download | assembler-80da2af821385b2fc89091e9ac37a047349da4bd.zip |
Implement source unit compilation, symbol resolution, error reporting
This library can now carry out all stages of assembly from collecting
source fragments to resolving symbols to pruning unused libraries to
generating a single compiled source file.
Pretty-printing of state has also been implemented in this library.
The source tree hierarchy, symbol resolution errors, and file read
errors can all be printed in a tidy format.
Diffstat (limited to 'src/errors')
-rw-r--r-- | src/errors/file_error.rs | 41 | ||||
-rw-r--r-- | src/errors/merge_error.rs | 41 | ||||
-rw-r--r-- | src/errors/mod.rs | 71 | ||||
-rw-r--r-- | src/errors/resolver_error.rs | 30 |
4 files changed, 183 insertions, 0 deletions
diff --git a/src/errors/file_error.rs b/src/errors/file_error.rs new file mode 100644 index 0000000..e601f94 --- /dev/null +++ b/src/errors/file_error.rs @@ -0,0 +1,41 @@ +pub use std::path::{Path, PathBuf}; + + +pub enum FileError { + InvalidExtension, + NotFound, + NotReadable, + IsADirectory, + InvalidUtf8, + Unknown, +} + +impl std::fmt::Debug for FileError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + let message = match self { + Self::InvalidExtension => "File has invalid extension", + Self::NotFound => "File was not found", + Self::InvalidUtf8 => "File does not contain valid UTF-8 text", + Self::NotReadable => "File is not readable", + Self::IsADirectory => "File is a directory", + Self::Unknown => "Unknown error while attempting to read from path", + }; + write!(f, "{message}") + } +} + + +pub fn read_file(path: &Path) -> Result<String, FileError> { + match std::fs::read(&path) { + Ok(bytes) => match String::from_utf8(bytes) { + Ok(source) => Ok(source), + Err(_) => return Err(FileError::InvalidUtf8), + } + Err(err) => return Err( match err.kind() { + std::io::ErrorKind::NotFound => FileError::NotFound, + std::io::ErrorKind::PermissionDenied => FileError::NotReadable, + std::io::ErrorKind::IsADirectory => FileError::IsADirectory, + _ => FileError::Unknown, + } ) + } +} diff --git a/src/errors/merge_error.rs b/src/errors/merge_error.rs new file mode 100644 index 0000000..a694b71 --- /dev/null +++ b/src/errors/merge_error.rs @@ -0,0 +1,41 @@ +use crate::*; + +use ansi::*; +use log::error; + + +pub struct MergeError<'a> { + pub resolver: &'a Resolver, + /// A list of source units involved in a cycle. + pub cyclic_unit_ids: Vec<usize>, +} + +impl MergeError<'_> { + pub fn report(&self) { + error!("A cyclic dependency was found between the following libraries:"); + for id in &self.cyclic_unit_ids { + if let Some(unit) = self.resolver.source_units.get(*id) { + let path = &unit.source_unit.path(); + match unit.source_unit.name() { + Some(name) => + eprintln!("{name}{NORMAL}{DIM} ({path}){NORMAL}"), + None => + eprintln!("{path}"), + }; + // Print each parent involved in the dependency cycle. + for parent_id in &unit.parent_ids { + if !self.cyclic_unit_ids.contains(parent_id) { continue; } + if let Some(parent_unit) = self.resolver.source_units.get(*parent_id) { + let parent_path = &parent_unit.source_unit.path(); + match parent_unit.source_unit.name() { + Some(parent_name) => + eprintln!(" => {parent_name} {DIM}({parent_path}){NORMAL}"), + None => + eprintln!(" => {parent_path}"), + }; + } + } + } + } + } +} diff --git a/src/errors/mod.rs b/src/errors/mod.rs new file mode 100644 index 0000000..b0bf7e4 --- /dev/null +++ b/src/errors/mod.rs @@ -0,0 +1,71 @@ +mod file_error; +mod merge_error; +mod resolver_error; + +pub use file_error::*; +pub use merge_error::*; +pub use resolver_error::*; + +use crate::*; + +use ansi::*; +use log::LogLevel; + + +pub fn report_source_issue(level: LogLevel, context: &Context, message: &str) { + // Prepare variables. + let in_merged = &context.source.in_merged; + let line_num = in_merged.start.line + 1; + let digits = line_num.to_string().len(); + let w = digits + 3; + let arrow = "-->"; + let mut string = message.to_string(); + + macro_rules! push { + ($($tokens:tt)*) => { string.push_str(&format!($($tokens)*)) }; + } + + // Format message and locations. + push!("{NORMAL}\n"); + push!("{BLUE}{arrow:>w$}{NORMAL} {in_merged}\n", w=w); + if let Some(in_source) = &context.source.in_source { + push!("{BLUE}{arrow:>w$}{NORMAL} {in_source}\n", w=w); + } + + // Format source context. + let left = in_merged.start.column; + let right = in_merged.end.column + 1; + let source_line = context.source_code.split('\n').nth(in_merged.start.line) + .unwrap_or("<error reading line from source>"); + let space = " "; + let colour = match level { + LogLevel::Info => BLUE, + LogLevel::Warn => YELLOW, + LogLevel::Error => RED, + LogLevel::Fatal => RED, + }; + + // Print source code line. + push!("{BLUE} {line_num} | {NORMAL}"); + for (i, c) in source_line.chars().enumerate() { + if i == left { push!("{colour}") } + if i == right { push!("{NORMAL}") } + push!("{c}"); + } + push!("{NORMAL}\n"); + + // Print source code underline. + push!("{BLUE} {space:>w$} | {NORMAL}", w=digits); + for _ in 0..left { push!(" "); } + push!("{colour}"); + for _ in left..right { push!("^"); } + push!("{NORMAL}"); + + // Print the completed message. + match level { + LogLevel::Info => log::info!( "{}", string), + LogLevel::Warn => log::warn!( "{}", string), + LogLevel::Error => log::error!("{}", string), + LogLevel::Fatal => log::fatal!("{}", string), + } +} diff --git a/src/errors/resolver_error.rs b/src/errors/resolver_error.rs new file mode 100644 index 0000000..de8b8d1 --- /dev/null +++ b/src/errors/resolver_error.rs @@ -0,0 +1,30 @@ +use crate::*; + +use log::LogLevel; + + +pub struct ResolverError<'a> { + pub resolver: &'a Resolver, +} + +impl<'a> ResolverError<'a> { + pub fn report(&self) { + for reference in &self.resolver.unresolved { + let message = format!( + "Undefined symbol, no label or macro has been defined with the name {:?}", + &reference.symbol.source.string, + ); + let context = reference.context(&self.resolver); + report_source_issue(LogLevel::Error, &context, &message); + } + for redefinition in &self.resolver.redefinitions { + let definition = self.resolver.definitions.get(redefinition.1).unwrap(); + let message = format!( + "Redefined symbol, first defined at {}", + &definition.symbol.source.in_merged, + ); + let context = redefinition.0.context(&self.resolver); + report_source_issue(LogLevel::Error, &context, &message); + } + } +} |