Skip to main content

microcad_lang_base/diag/
mod.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Handling of diagnostic errors.
5//!
6//! While *evaluation* µcad is collecting [`Diagnostic`] messages.
7//!
8//! This is done in [`DiagHandler`] by providing the following traits:
9//!
10//! - [`PushDiag`]: Collects error in [`DiagHandler`]
11//! - [`Diag`]: Get diagnostic messages
12
13mod diag_handler;
14mod diagnostic;
15mod diagnostics;
16mod level;
17
18pub use diag_handler::*;
19pub use diagnostic::*;
20pub use diagnostics::*;
21pub use level::*;
22use miette::{Diagnostic as MietteDiagnostic, Report};
23
24use thiserror::Error;
25
26use crate::src_ref::*;
27use microcad_core::hash::HashSet;
28
29/// Diagnostic error.
30#[derive(Debug, Error, MietteDiagnostic)]
31pub enum DiagError {
32    /// Cannot continue evaluation after error limit has been reached.
33    #[error("Error limit reached: Stopped evaluation after {0} errors")]
34    ErrorLimitReached(u32),
35}
36
37/// Result type of any resolve.
38pub type DiagResult<T> = std::result::Result<T, DiagError>;
39
40/// A trait to add diagnostics with different levels conveniently.
41pub trait PushDiag {
42    /// Push a diagnostic message (must be implemented).
43    fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()>;
44
45    /// Push new trace message.
46    fn trace(&mut self, src: &impl SrcReferrer, message: String) {
47        self.push_diag(Diagnostic::Trace(
48            Refer::new(Report::msg(message), src.src_ref()).into(),
49        ))
50        .expect("could not push diagnostic trace message");
51    }
52    /// Push new informative message.
53    fn info(&mut self, src: &impl SrcReferrer, message: String) {
54        self.push_diag(Diagnostic::Info(
55            Refer::new(Report::msg(message), src.src_ref()).into(),
56        ))
57        .expect("could not push diagnostic info message");
58    }
59    /// Push new warning.
60    fn warning(&mut self, src: &impl SrcReferrer, err: impl Into<Report>) -> DiagResult<()> {
61        let err = Diagnostic::Warning(Refer::new(err.into(), src.src_ref()).into());
62        if cfg!(feature = "ansi-color") {
63            log::warn!("{}", color_print::cformat!("<y,s>{err}</>"));
64        } else {
65            log::warn!("{err}");
66        }
67        self.push_diag(err)
68    }
69    /// Push new error.
70    fn error(&mut self, src: &impl SrcReferrer, err: impl Into<Report>) -> DiagResult<()> {
71        let err = Diagnostic::Error(Refer::new(err.into(), src.src_ref()).into());
72        if cfg!(feature = "ansi-color") {
73            log::error!("{}", color_print::cformat!("<r,s>{err}</>"));
74        } else {
75            log::error!("{err}");
76        }
77        self.push_diag(err)
78    }
79}
80
81/// Diagnosis trait gives access about collected errors.
82pub trait Diag {
83    /// Pretty print all errors.
84    fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result;
85
86    /// Pretty write all errors into a file.
87    fn write_diagnosis(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> {
88        write!(w, "{}", self.diagnosis())
89    }
90
91    /// Get pretty printed errors as string.
92    fn diagnosis(&self) -> String {
93        let mut str = String::new();
94        self.fmt_diagnosis(&mut str).expect("displayable diagnosis");
95        str
96    }
97
98    /// Returns true if there are warnings.
99    fn has_warnings(&self) -> bool {
100        self.warning_count() > 0
101    }
102
103    /// Return number of occurred warnings.
104    fn warning_count(&self) -> u32;
105
106    /// Returns true if there are errors.
107    fn has_errors(&self) -> bool {
108        self.error_count() > 0
109    }
110
111    /// Return number of occurred errors.
112    fn error_count(&self) -> u32;
113
114    /// Return all lines with errors
115    fn error_lines(&self) -> HashSet<u32>;
116
117    /// Return all lines with warnings
118    fn warning_lines(&self) -> HashSet<u32>;
119}