antlr-rust-runtime 0.15.0

High performance Rust runtime and target support for ANTLR v4 generated parsers
Documentation
use std::fmt;
use std::path::PathBuf;

use super::frontend::SourceSpan;
use super::source::SourceSet;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum Severity {
    Warning,
    Error,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RelatedDiagnostic {
    pub(crate) span: SourceSpan,
    pub(crate) message: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Diagnostic {
    pub(crate) code: &'static str,
    pub(crate) severity: Severity,
    pub(crate) message: String,
    pub(crate) primary: SourceSpan,
    pub(crate) related: Vec<RelatedDiagnostic>,
}

impl Diagnostic {
    pub(crate) fn error(
        code: &'static str,
        primary: SourceSpan,
        message: impl Into<String>,
    ) -> Self {
        Self {
            code,
            severity: Severity::Error,
            message: message.into(),
            primary,
            related: Vec::new(),
        }
    }

    pub(crate) fn warning(
        code: &'static str,
        primary: SourceSpan,
        message: impl Into<String>,
    ) -> Self {
        Self {
            code,
            severity: Severity::Warning,
            message: message.into(),
            primary,
            related: Vec::new(),
        }
    }

    #[must_use]
    pub(crate) fn with_related(mut self, span: SourceSpan, message: impl Into<String>) -> Self {
        self.related.push(RelatedDiagnostic {
            span,
            message: message.into(),
        });
        self
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct CompilationError {
    diagnostics: Vec<Diagnostic>,
    locations: Vec<Option<DiagnosticLocation>>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct DiagnosticLocation {
    pub(crate) path: PathBuf,
    pub(crate) position: Option<(usize, usize)>,
}

impl CompilationError {
    pub(crate) fn new(diagnostics: Vec<Diagnostic>) -> Self {
        debug_assert!(
            diagnostics
                .iter()
                .any(|diagnostic| diagnostic.severity == Severity::Error)
        );
        let locations = vec![None; diagnostics.len()];
        Self {
            diagnostics,
            locations,
        }
    }

    pub(crate) fn with_sources(mut self, sources: &SourceSet) -> Self {
        for (diagnostic, location) in self.diagnostics.iter().zip(&mut self.locations) {
            if location.is_some() {
                continue;
            }
            let source = diagnostic.primary.source;
            let Some(path) = sources.logical_path(source) else {
                continue;
            };
            *location = Some(DiagnosticLocation {
                path: path.to_path_buf(),
                position: sources.line_column(source, diagnostic.primary.bytes.start),
            });
        }
        self
    }

    pub(crate) fn diagnostics(&self) -> &[Diagnostic] {
        &self.diagnostics
    }

    pub(crate) fn location(&self, index: usize) -> Option<&DiagnosticLocation> {
        self.locations.get(index).and_then(Option::as_ref)
    }
}

impl fmt::Display for CompilationError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let errors = self
            .diagnostics
            .iter()
            .filter(|diagnostic| diagnostic.severity == Severity::Error)
            .count();
        write!(
            formatter,
            "grammar compilation failed with {errors} error(s)"
        )
    }
}

impl std::error::Error for CompilationError {}