core: Add Diagnostic class
This commit is contained in:
parent
593555228c
commit
1c071a294c
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue