core: Add Diagnostic class

This commit is contained in:
David Mak 2023-11-14 18:43:22 +08:00
parent 593555228c
commit 1c071a294c
1 changed files with 251 additions and 0 deletions

View File

@ -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<Ordering> {
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<H: Hasher>(&self, state: &mut H) {
self.to_string().hash(state)
}
}
#[deprecated]
pub fn contains_errors(diagnostics: &HashSet<Diagnostic>) -> 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<T> = 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<T> {
value: T,
engine: DiagnosticEngine,
}
impl <T> Into<DiagnosticsResult<T>> for DiagnosticsPartialResult<T> {
fn into(self) -> DiagnosticsResult<T> {
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<Diagnostic>,
}
impl DiagnosticEngine {
pub fn new() -> DiagnosticEngine {
Self::default()
}
pub fn from(s: HashSet<Diagnostic>) -> 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<I: IntoIterator<Item = Diagnostic>>(&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<T>(&mut self, result: DiagnosticsPartialResult<T>) -> T {
self.merge_with(result.engine);
result.value
}
pub fn consume_result<T>(&mut self, result: DiagnosticsResult<T>) -> Result<T, Self> {
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<T>(&mut self, result: DiagnosticsResult<T>) -> Option<T> {
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<Diagnostic> {
self.diagnostics.into_iter()
}
pub fn into_partial_result<T>(self, value: T) -> DiagnosticsPartialResult<T> {
DiagnosticsPartialResult {
value,
engine: self
}
}
pub fn into_result<T>(self, value: T) -> DiagnosticsResult<T> {
if self.has_errors() {
Err(self)
} else {
Ok((value, self))
}
}
}