From 80da2af821385b2fc89091e9ac37a047349da4bd Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Wed, 5 Feb 2025 12:58:02 +1300 Subject: 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. --- src/errors/file_error.rs | 41 +++++++++++++++++++++++++ src/errors/merge_error.rs | 41 +++++++++++++++++++++++++ src/errors/mod.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++ src/errors/resolver_error.rs | 30 +++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 src/errors/file_error.rs create mode 100644 src/errors/merge_error.rs create mode 100644 src/errors/mod.rs create mode 100644 src/errors/resolver_error.rs (limited to 'src/errors') 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 { + 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, +} + +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(""); + 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); + } + } +} -- cgit v1.2.3-70-g09d2