use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use zerodds_idl_cst::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum DiagnosticCode {
UnexpectedToken,
MissingToken,
UnterminatedDeclaration,
InvalidToken,
VendorExtension,
Unsupported,
InvalidAnnotation,
DuplicateName,
AmbiguousName,
UnresolvedName,
InvalidDiscriminator,
InvalidCaseLabel,
ConflictingAnnotations,
InvalidBound,
UndefinedForward,
DuplicateMemberId,
KeywordCollision,
PolicyAdvice,
TypeMismatch,
ErrorDirective,
InvalidInheritance,
Preprocessing,
}
impl DiagnosticCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::UnexpectedToken => "IDL0001",
Self::MissingToken => "IDL0002",
Self::UnterminatedDeclaration => "IDL0003",
Self::InvalidToken => "IDL0004",
Self::VendorExtension => "IDL0005",
Self::Unsupported => "IDL0006",
Self::InvalidAnnotation => "IDL0007",
Self::DuplicateName => "IDL0008",
Self::AmbiguousName => "IDL0009",
Self::UnresolvedName => "IDL0010",
Self::InvalidDiscriminator => "IDL0011",
Self::InvalidCaseLabel => "IDL0012",
Self::ConflictingAnnotations => "IDL0013",
Self::InvalidBound => "IDL0014",
Self::UndefinedForward => "IDL0015",
Self::DuplicateMemberId => "IDL0016",
Self::KeywordCollision => "IDL0017",
Self::PolicyAdvice => "IDL0018",
Self::TypeMismatch => "IDL0019",
Self::ErrorDirective => "IDL0020",
Self::InvalidInheritance => "IDL0021",
Self::Preprocessing => "IDL0022",
}
}
pub const ALL: [Self; 22] = [
Self::UnexpectedToken,
Self::MissingToken,
Self::UnterminatedDeclaration,
Self::InvalidToken,
Self::VendorExtension,
Self::Unsupported,
Self::InvalidAnnotation,
Self::DuplicateName,
Self::AmbiguousName,
Self::UnresolvedName,
Self::InvalidDiscriminator,
Self::InvalidCaseLabel,
Self::ConflictingAnnotations,
Self::InvalidBound,
Self::UndefinedForward,
Self::DuplicateMemberId,
Self::KeywordCollision,
Self::PolicyAdvice,
Self::TypeMismatch,
Self::ErrorDirective,
Self::InvalidInheritance,
Self::Preprocessing,
];
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub severity: Severity,
pub code: DiagnosticCode,
pub message: String,
pub primary: Span,
pub labels: Vec<(Span, String)>,
pub file: Option<String>,
pub spec_reference: &'static str,
}
impl Diagnostic {
#[must_use]
pub fn error(
code: DiagnosticCode,
primary: Span,
message: String,
spec_reference: &'static str,
) -> Self {
Self {
severity: Severity::Error,
code,
message,
primary,
labels: Vec::new(),
file: None,
spec_reference,
}
}
#[must_use]
pub fn cites_a_clause(&self) -> bool {
self.spec_reference.contains('§') && !self.spec_reference.starts_with("zerodds")
}
#[must_use]
pub fn warning(
code: DiagnosticCode,
primary: Span,
message: String,
spec_reference: &'static str,
) -> Self {
Self {
severity: Severity::Warning,
code,
message,
primary,
labels: Vec::new(),
file: None,
spec_reference,
}
}
#[must_use]
pub fn with_label(mut self, span: Span, note: String) -> Self {
self.labels.push((span, note));
self
}
#[must_use]
pub const fn is_error(&self) -> bool {
matches!(self.severity, Severity::Error)
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} [{}] at {}: {} ({})",
match self.severity {
Severity::Error => "error",
Severity::Warning => "warning",
},
self.code.as_str(),
self.primary,
self.message,
self.spec_reference
)
}
}
impl core::error::Error for Diagnostic {}
#[must_use]
pub fn refused_by_a_clause(diagnostics: &[Diagnostic]) -> bool {
let mut errors = diagnostics
.iter()
.filter(|d| d.severity == Severity::Error)
.peekable();
errors.peek().is_some() && errors.all(Diagnostic::cites_a_clause)
}
#[cfg(test)]
mod clause_tests {
use super::*;
fn at(severity: Severity, spec_reference: &'static str) -> Diagnostic {
Diagnostic {
severity,
code: DiagnosticCode::InvalidInheritance,
message: String::new(),
primary: Span::new(0, 0),
labels: Vec::new(),
file: None,
spec_reference,
}
}
#[test]
fn a_clause_of_an_omg_specification_refuses_and_zerodds_policy_does_not() {
let clause = at(Severity::Error, "DDS-XTypes 1.3 §7.2.2.4.5");
let idl = at(Severity::Error, "OMG IDL 4.2 §7.5.2");
let policy = at(Severity::Error, "zerodds-ast-1.0 §8.1");
let warning = at(Severity::Warning, "zerodds-idl-frontend-1.0 §3");
assert!(clause.cites_a_clause() && idl.cites_a_clause());
assert!(!policy.cites_a_clause());
assert!(refused_by_a_clause(&[clause.clone(), idl.clone()]));
assert!(refused_by_a_clause(&[clause.clone(), warning.clone()]));
assert!(!refused_by_a_clause(&[clause, policy]));
assert!(!refused_by_a_clause(&[warning]));
assert!(!refused_by_a_clause(&[]));
}
}