microcad-lang-base 0.5.0

µcad language basics
Documentation
// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

use crate::{GetSourceLocInfoByHash, diag::*};
use microcad_core::hash::HashSet;
use std::io::IsTerminal;

/// Handler for diagnostics.
#[derive(Default)]
pub struct DiagHandler {
    /// The list of diagnostics per source file.
    pub diagnostics: Diagnostics,
    /// The maximum number of collected errors until abort
    /// (`0` means unlimited number of errors).
    error_limit: Option<u32>,
    /// `true` after the first time error limit was reached
    error_limit_reached: bool,
    /// Treat warnings as errors if `true`.
    warnings_as_errors: bool,
    /// Diagnostic rendering options
    pub render_options: DiagRenderOptions,
}

/// Options that control the rendering of diagnostics
#[derive(Debug)]
pub struct DiagRenderOptions {
    /// Render diagnostic with colors
    pub color: bool,
    /// Render diagnostic with unicode characters
    pub unicode: bool,
}

impl Default for DiagRenderOptions {
    fn default() -> Self {
        DiagRenderOptions {
            color: std::env::var("NO_COLOR").as_deref().unwrap_or("0") == "0",
            unicode: std::io::stdout().is_terminal() && std::io::stderr().is_terminal(),
        }
    }
}

impl DiagRenderOptions {
    /// Get the miette theme for the options
    pub fn theme(&self) -> miette::GraphicalTheme {
        match (self.unicode, self.color) {
            (true, true) => miette::GraphicalTheme::unicode(),
            (true, false) => miette::GraphicalTheme::unicode_nocolor(),
            (false, true) => miette::GraphicalTheme::ascii(),
            (false, false) => miette::GraphicalTheme::none(),
        }
    }
}

/// Handler for diagnostics.
impl DiagHandler {
    /// Pretty print all errors of all files.
    pub fn pretty_print(
        &self,
        f: &mut dyn std::fmt::Write,
        source_by_hash: &impl GetSourceLocInfoByHash,
    ) -> std::fmt::Result {
        self.diagnostics
            .pretty_print(f, source_by_hash, &self.render_options)
    }

    /// Return overall number of occurred errors.
    pub fn warning_count(&self) -> u32 {
        self.diagnostics.warning_count()
    }

    /// Return overall number of occurred errors.
    pub fn error_count(&self) -> u32 {
        self.diagnostics.error_count()
    }

    /// return lines with errors
    pub fn error_lines(&self) -> HashSet<u32> {
        self.diagnostics.error_lines()
    }

    /// return lines with warnings
    pub fn warning_lines(&self) -> HashSet<u32> {
        self.diagnostics.warning_lines()
    }

    /// Clear all errors and warnings
    pub fn clear(&mut self) {
        self.diagnostics.clear();
    }
}

impl PushDiag for DiagHandler {
    fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
        if let Some(error_limit) = self.error_limit {
            if self.error_count() >= error_limit && !self.error_limit_reached {
                self.error(&SrcRef::none(), DiagError::ErrorLimitReached(error_limit))?;
                self.error_limit_reached = true;
            }
            return Err(DiagError::ErrorLimitReached(error_limit));
        }

        let diag = match diag {
            Diagnostic::Warning(refer) if self.warnings_as_errors => Diagnostic::Error(refer),
            diag => diag,
        };

        self.diagnostics.push_diag(diag)
    }
}