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}