diff --git a/nac3core/src/lib.rs b/nac3core/src/lib.rs index bff6b5c..f31dd35 100644 --- a/nac3core/src/lib.rs +++ b/nac3core/src/lib.rs @@ -1,7 +1,258 @@ #![warn(clippy::all)] #![allow(dead_code)] +use std::cmp::Ordering; +use std::collections::hash_set::{IntoIter, Iter}; +use std::collections::HashSet; +use self::Diagnostic::*; + +use std::fmt; +use std::hash::{Hash, Hasher}; +use nac3parser::ast::Location; + pub mod codegen; pub mod symbol_resolver; pub mod toplevel; pub mod typecheck; + +#[derive(Clone, Eq)] +pub enum Diagnostic { + /// A diagnostic warning. + DiagWarn(String, Location), + + /// A diagnostic error. + DiagErr(String, Location), +} + +impl Diagnostic { + + pub fn to_string(&self) -> String { + + let diag_type = match self { + DiagErr(_, _) => "error", + DiagWarn(_, _) => "warning", + }; + let msg = self.message(); + let loc = self.location(); + + format!("{loc}: {diag_type}: {msg}") + } + + /// Returns the message of this [Diagnostic]. + fn message(&self) -> &str { + match self { + DiagErr(msg, _) => msg, + DiagWarn(msg, _) => msg, + }.as_str() + } + + /// Returns the location of where this [Diagnostic] is created from. + fn location(&self) -> &Location { + match self { + DiagErr(_, loc) => loc, + DiagWarn(_, loc) => loc, + } + } +} + +impl Ord for Diagnostic { + fn cmp(&self, other: &Self) -> Ordering { + let loc_cmp = self.location().cmp(other.location()); + if loc_cmp != Ordering::Equal { + return loc_cmp + } + + let diag_type_cmp = match self { + DiagWarn(_, _) => match other { + DiagWarn(_, _) => Ordering::Equal, + DiagErr(_, _) => Ordering::Less, + } + + DiagErr(_, _) => match other { + DiagWarn(_, _) => Ordering::Greater, + DiagErr(_, _) => Ordering::Equal, + } + }; + if diag_type_cmp != Ordering::Equal { + return diag_type_cmp + } + + self.message().cmp(other.message()) + } +} + +impl PartialOrd for Diagnostic { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Diagnostic { + fn eq(&self, other: &Self) -> bool { + match self { + DiagErr(msg, loc) => { + if let DiagErr(other_msg, other_loc) = other { + msg == other_msg && loc == other_loc + } else { + false + } + } + + DiagWarn(msg, loc) => { + if let DiagWarn(other_msg, other_loc) = other { + msg == other_msg && loc == other_loc + } else { + false + } + } + } + } +} + +impl fmt::Display for Diagnostic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +impl Hash for Diagnostic { + fn hash(&self, state: &mut H) { + self.to_string().hash(state) + } +} + +#[deprecated] +pub fn contains_errors(diagnostics: &HashSet) -> bool { + diagnostics.iter().any(|d| match d { + DiagErr(_, _) => true, + _ => false, + }) +} + +/// Type alias for [Result] encapsulating a success value of type [T], and any diagnostics emitted +/// during the generation of the result. +/// +/// This type should be used where a result cannot be meaningfully generated when a subset of the +/// possible diagnostics is emitted. +pub type DiagnosticsResult = Result<(T, DiagnosticEngine), DiagnosticEngine>; + +/// Type representing a result of type `T` with any diagnostics emitted during the generation of the +/// result. +/// +/// This type should be used where, even when diagnostics are generated, a meaningful result can +/// still be generated. +pub struct DiagnosticsPartialResult { + value: T, + engine: DiagnosticEngine, +} + +impl Into> for DiagnosticsPartialResult { + fn into(self) -> DiagnosticsResult { + if self.engine.has_errors() { + Err(self.engine) + } else { + Ok((self.value, self.engine)) + } + } +} + +/// A simple abstraction over a set of collected diagnostics. +#[derive(Clone, Default)] +pub struct DiagnosticEngine { + diagnostics: HashSet, +} + +impl DiagnosticEngine { + pub fn new() -> DiagnosticEngine { + Self::default() + } + + pub fn from(s: HashSet) -> DiagnosticEngine { + DiagnosticEngine { + diagnostics: s + } + } + + pub fn single(diag: Diagnostic) -> DiagnosticEngine { + Self::from(HashSet::from([diag])) + } + + pub fn is_empty(&self) -> bool { + self.diagnostics.is_empty() + } + + pub fn has_errors(&self) -> bool { + self.diagnostics.iter().any(|d| match d { + DiagErr(_, _) => true, + _ => false, + }) + } + + pub fn insert(&mut self, diag: Diagnostic) { + self.diagnostics.insert(diag); + } + + pub fn extend>(&mut self, diags: I) { + self.diagnostics.extend(diags); + } + + pub fn merge_with(&mut self, other: DiagnosticEngine) { + self.extend(other.diagnostics) + } + + pub fn consume_partial_result(&mut self, result: DiagnosticsPartialResult) -> T { + self.merge_with(result.engine); + result.value + } + + pub fn consume_result(&mut self, result: DiagnosticsResult) -> Result { + match result { + Ok((v, diags)) => { + self.merge_with(diags); + Ok(v) + } + + Err(diags) => { + self.merge_with(diags); + Err(self.clone()) + } + } + } + + pub fn consume_result_to_option(&mut self, result: DiagnosticsResult) -> Option { + match result { + Ok((v, diags)) => { + self.merge_with(diags); + Some(v) + } + + Err(diags) => { + self.merge_with(diags); + None + } + } + } + + pub fn iter(&self) -> Iter<'_, Diagnostic> { + self.diagnostics.iter() + } + + pub fn into_iter(self) -> IntoIter { + self.diagnostics.into_iter() + } + + pub fn into_partial_result(self, value: T) -> DiagnosticsPartialResult { + DiagnosticsPartialResult { + value, + engine: self + } + } + + pub fn into_result(self, value: T) -> DiagnosticsResult { + if self.has_errors() { + Err(self) + } else { + Ok((value, self)) + } + } +}