Skip to main content

yaml_rt_core/
diagnostic.rs

1use std::fmt;
2
3use crate::{LineCol, Source, Span};
4
5/// Error type for YAML parsing, semantic lookup, typed overlays, and emission.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct YamlError {
8    /// Primary diagnostic.
9    pub diagnostic: Diagnostic,
10}
11
12impl YamlError {
13    /// Creates a new error from a diagnostic.
14    #[must_use]
15    pub const fn new(diagnostic: Diagnostic) -> Self {
16        Self { diagnostic }
17    }
18
19    /// Adds line/column information from `source` when the diagnostic does not
20    /// already have a position.
21    #[must_use]
22    pub fn with_position_from(mut self, source: &Source) -> Self {
23        if self.diagnostic.position.is_none() {
24            self.diagnostic.position = Some(source.diagnostic_position(&self.diagnostic));
25        }
26        self
27    }
28}
29
30impl fmt::Display for YamlError {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        self.diagnostic.fmt(formatter)
33    }
34}
35
36impl std::error::Error for YamlError {}
37
38/// Structured user-facing diagnostic.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Diagnostic {
41    /// Error phase.
42    pub kind: DiagnosticKind,
43    /// Primary message.
44    pub message: String,
45    /// Primary source span.
46    pub span: Span,
47    /// One-based line/column for the primary span when a source is available.
48    pub position: Option<LineCol>,
49    /// Expected syntax or semantic items.
50    pub expected: Vec<String>,
51    /// Additional context notes.
52    pub notes: Vec<String>,
53}
54
55impl Diagnostic {
56    /// Creates a diagnostic with no expected items or notes.
57    #[must_use]
58    pub fn new(kind: DiagnosticKind, message: impl Into<String>, span: Span) -> Self {
59        Self {
60            kind,
61            message: message.into(),
62            span,
63            position: None,
64            expected: Vec::new(),
65            notes: Vec::new(),
66        }
67    }
68
69    /// Sets a one-based line/column position for the primary span.
70    #[must_use]
71    pub const fn with_position(mut self, position: LineCol) -> Self {
72        self.position = Some(position);
73        self
74    }
75
76    /// Adds one expected syntax or semantic item.
77    #[must_use]
78    pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
79        self.expected.push(expected.into());
80        self
81    }
82
83    /// Adds one explanatory note.
84    #[must_use]
85    pub fn with_note(mut self, note: impl Into<String>) -> Self {
86        self.notes.push(note.into());
87        self
88    }
89}
90
91impl fmt::Display for Diagnostic {
92    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(formatter, "{:?}: {}", self.kind, self.message)?;
94
95        if let Some(position) = self.position {
96            write!(formatter, " at {}:{}", position.line, position.column)?;
97        }
98
99        if !self.expected.is_empty() {
100            write!(formatter, " (expected: {})", self.expected.join(", "))?;
101        }
102
103        for note in &self.notes {
104            write!(formatter, "\nnote: {note}")?;
105        }
106
107        Ok(())
108    }
109}
110
111/// Diagnostic phase.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum DiagnosticKind {
114    /// Source validation failure.
115    Source,
116    /// Lexer failure.
117    Lexer,
118    /// Parser failure.
119    Parser,
120    /// Semantic graph or schema failure.
121    Semantic,
122    /// Typed overlay failure.
123    Typed,
124    /// Emitter failure.
125    Emitter,
126}
127
128/// Alias for parse errors until richer phase-specific errors are introduced.
129pub type ParseError = YamlError;