summaryrefslogtreecommitdiff
path: root/src/errors
diff options
context:
space:
mode:
Diffstat (limited to 'src/errors')
-rw-r--r--src/errors/file_error.rs41
-rw-r--r--src/errors/merge_error.rs41
-rw-r--r--src/errors/mod.rs71
-rw-r--r--src/errors/resolver_error.rs30
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);
+ }
+ }
+}