Skip to main content

rd_source/
diagnostic.rs

1use std::{fmt, ops::Range};
2
3use rd_ast::RdDocument;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct SourcePosition {
7    line: u32,
8    column: u32,
9}
10impl SourcePosition {
11    pub fn line(&self) -> u32 {
12        self.line
13    }
14    pub fn column(&self) -> u32 {
15        self.column
16    }
17}
18impl SourcePosition {
19    pub(crate) fn new(line: u32, column: u32) -> Self {
20        Self { line, column }
21    }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct SourceSpan {
26    bytes: Range<usize>,
27    start: SourcePosition,
28    end: SourcePosition,
29}
30impl SourceSpan {
31    pub(crate) fn new(bytes: Range<usize>, start: SourcePosition, end: SourcePosition) -> Self {
32        Self { bytes, start, end }
33    }
34}
35impl SourceSpan {
36    pub fn bytes(&self) -> Range<usize> {
37        self.bytes.clone()
38    }
39    pub fn start(&self) -> &SourcePosition {
40        &self.start
41    }
42    pub fn end(&self) -> &SourcePosition {
43        &self.end
44    }
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct Parsed {
49    document: RdDocument,
50    diagnostics: Vec<Diagnostic>,
51}
52impl Parsed {
53    pub(crate) fn new(document: RdDocument, diagnostics: Vec<Diagnostic>) -> Self {
54        Self {
55            document,
56            diagnostics,
57        }
58    }
59    pub fn document(&self) -> &RdDocument {
60        &self.document
61    }
62    pub fn diagnostics(&self) -> &[Diagnostic] {
63        &self.diagnostics
64    }
65    pub fn into_parts(self) -> (RdDocument, Vec<Diagnostic>) {
66        (self.document, self.diagnostics)
67    }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Diagnostic {
72    severity: Severity,
73    code: DiagnosticCode,
74    message: String,
75    span: SourceSpan,
76}
77impl Diagnostic {
78    pub(crate) fn new(
79        severity: Severity,
80        code: DiagnosticCode,
81        message: impl Into<String>,
82        span: SourceSpan,
83    ) -> Self {
84        Self {
85            severity,
86            code,
87            message: message.into(),
88            span,
89        }
90    }
91    pub fn severity(&self) -> &Severity {
92        &self.severity
93    }
94    pub fn code(&self) -> &DiagnosticCode {
95        &self.code
96    }
97    pub fn message(&self) -> &str {
98        &self.message
99    }
100    pub fn span(&self) -> &SourceSpan {
101        &self.span
102    }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[non_exhaustive]
107pub enum Severity {
108    Warning,
109    Error,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113#[non_exhaustive]
114pub enum DiagnosticCode {
115    UnexpectedClosingDelimiter,
116    UnexpectedOpeningDelimiter,
117    UnclosedGroup,
118    UnclosedOption,
119    MissingArgument,
120    UnknownTag,
121    TagNotAllowedHere,
122    InvalidOptionSyntax,
123    InvalidArity,
124    UnexpectedToken,
125    UnexpectedEndIf,
126    MissingEndIf,
127    UnexpectedConditional,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum ParseError {
133    InvalidUtf8 {
134        valid_up_to: usize,
135        error_len: Option<usize>,
136    },
137    NulByte {
138        offset: usize,
139    },
140    UnsupportedEncoding {
141        name: String,
142        span: Option<SourceSpan>,
143    },
144    InputTooLarge,
145    NestingLimitExceeded {
146        span: SourceSpan,
147    },
148}
149impl fmt::Display for ParseError {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::InvalidUtf8 {
153                valid_up_to,
154                error_len,
155            } => write!(f, "invalid UTF-8 at {valid_up_to} (length {error_len:?})"),
156            Self::NulByte { offset } => write!(f, "embedded NUL byte at {offset}"),
157            Self::UnsupportedEncoding { name, .. } => write!(f, "unsupported encoding: {name}"),
158            Self::InputTooLarge => f.write_str("input is too large"),
159            Self::NestingLimitExceeded { .. } => f.write_str("nesting limit exceeded"),
160        }
161    }
162}
163impl std::error::Error for ParseError {}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    #[test]
169    fn hard_input_errors_report_utf8_and_nul() {
170        assert_eq!(
171            crate::parse(b"a\0b"),
172            Err(ParseError::NulByte { offset: 1 })
173        );
174        assert_eq!(
175            crate::parse(&[b'a', 0xe2, 0x82]),
176            Err(ParseError::InvalidUtf8 {
177                valid_up_to: 1,
178                error_len: None
179            })
180        );
181        assert_eq!(
182            crate::parse(&[0xff]),
183            Err(ParseError::InvalidUtf8 {
184                valid_up_to: 0,
185                error_len: Some(1)
186            })
187        );
188    }
189    #[test]
190    fn empty_and_whitespace_documents_are_preserved() {
191        assert!(crate::parse(b"").unwrap().document().nodes().is_empty());
192        let document = crate::parse(b" \t\n").unwrap().into_parts().0;
193        assert_eq!(document.nodes(), &[rd_ast::RdNode::Text(" \t\n".into())]);
194    }
195}