1use core::fmt;
24
25use crate::grammar::TokenKind;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct Span {
34 pub start: usize,
36 pub end: usize,
38}
39
40impl Span {
41 pub const SYNTHETIC: Self = Self { start: 0, end: 0 };
44
45 #[must_use]
47 pub const fn new(start: usize, end: usize) -> Self {
48 Self { start, end }
49 }
50
51 #[must_use]
53 pub const fn point(at: usize) -> Self {
54 Self { start: at, end: at }
55 }
56
57 #[must_use]
59 pub const fn len(&self) -> usize {
60 self.end.saturating_sub(self.start)
61 }
62
63 #[must_use]
65 pub const fn is_empty(&self) -> bool {
66 self.start == self.end
67 }
68
69 #[must_use]
71 pub const fn merge(self, other: Self) -> Self {
72 Self {
73 start: if self.start < other.start {
74 self.start
75 } else {
76 other.start
77 },
78 end: if self.end > other.end {
79 self.end
80 } else {
81 other.end
82 },
83 }
84 }
85
86 #[must_use]
88 pub const fn contains_offset(&self, byte_offset: usize) -> bool {
89 byte_offset >= self.start && byte_offset < self.end
90 }
91}
92
93impl fmt::Display for Span {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(f, "{}..{}", self.start, self.end)
96 }
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
101pub enum Severity {
102 Help,
104 Note,
106 Warning,
108 Error,
110}
111
112impl fmt::Display for Severity {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 let label = match self {
115 Self::Help => "help",
116 Self::Note => "note",
117 Self::Warning => "warning",
118 Self::Error => "error",
119 };
120 f.write_str(label)
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct Label {
128 pub span: Span,
130 pub message: String,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct Diagnostic {
140 pub severity: Severity,
142 pub message: String,
144 pub primary_span: Span,
146 pub labels: Vec<Label>,
148}
149
150impl Diagnostic {
151 #[must_use]
153 pub fn error(message: impl Into<String>, primary_span: Span) -> Self {
154 Self {
155 severity: Severity::Error,
156 message: message.into(),
157 primary_span,
158 labels: Vec::new(),
159 }
160 }
161
162 #[must_use]
164 pub fn warning(message: impl Into<String>, primary_span: Span) -> Self {
165 Self {
166 severity: Severity::Warning,
167 message: message.into(),
168 primary_span,
169 labels: Vec::new(),
170 }
171 }
172
173 #[must_use]
175 pub fn with_label(mut self, span: Span, message: impl Into<String>) -> Self {
176 self.labels.push(Label {
177 span,
178 message: message.into(),
179 });
180 self
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum ParseError {
191 UnexpectedToken {
194 found: TokenKind,
196 expected: Vec<TokenKind>,
198 span: Span,
200 },
201 UnexpectedEof {
204 expected: Vec<TokenKind>,
206 span: Span,
208 },
209 LexerError {
212 message: String,
214 span: Span,
216 },
217}
218
219impl ParseError {
220 #[must_use]
222 pub fn to_diagnostic(&self) -> Diagnostic {
223 match self {
224 Self::UnexpectedToken {
225 found,
226 expected,
227 span,
228 } => {
229 let msg = format!(
230 "unexpected token {:?}, expected {}",
231 found,
232 format_expected(expected),
233 );
234 Diagnostic::error(msg, *span)
235 }
236 Self::UnexpectedEof { expected, span } => {
237 let msg = format!(
238 "unexpected end of input, expected {}",
239 format_expected(expected),
240 );
241 Diagnostic::error(msg, *span)
242 }
243 Self::LexerError { message, span } => Diagnostic::error(message.clone(), *span),
244 }
245 }
246
247 #[must_use]
249 pub const fn span(&self) -> Span {
250 match self {
251 Self::UnexpectedToken { span, .. }
252 | Self::UnexpectedEof { span, .. }
253 | Self::LexerError { span, .. } => *span,
254 }
255 }
256}
257
258fn format_expected(expected: &[TokenKind]) -> String {
261 match expected {
262 [] => "(nothing)".to_string(),
263 [single] => format!("{single:?}"),
264 many => {
265 let formatted: Vec<String> = many.iter().map(|t| format!("{t:?}")).collect();
266 format!("one of [{}]", formatted.join(", "))
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
280 fn span_new_stores_start_and_end() {
281 let s = Span::new(3, 7);
282 assert_eq!(s.start, 3);
283 assert_eq!(s.end, 7);
284 }
285
286 #[test]
287 fn span_point_has_zero_length() {
288 let s = Span::point(42);
289 assert_eq!(s.start, 42);
290 assert_eq!(s.end, 42);
291 assert!(s.is_empty());
292 assert_eq!(s.len(), 0);
293 }
294
295 #[test]
296 fn span_len_counts_bytes() {
297 assert_eq!(Span::new(0, 5).len(), 5);
298 assert_eq!(Span::new(10, 14).len(), 4);
299 }
300
301 #[test]
302 fn span_merge_takes_outer_bounds() {
303 let a = Span::new(2, 5);
304 let b = Span::new(4, 8);
305 assert_eq!(a.merge(b), Span::new(2, 8));
306 assert_eq!(b.merge(a), Span::new(2, 8));
308 }
309
310 #[test]
311 fn span_merge_with_synthetic_keeps_real_bounds() {
312 let real = Span::new(10, 20);
313 let merged = real.merge(Span::SYNTHETIC);
316 assert_eq!(merged, Span::new(0, 20));
317 }
318
319 #[test]
320 fn span_contains_offset_is_half_open() {
321 let s = Span::new(5, 10);
322 assert!(s.contains_offset(5));
323 assert!(s.contains_offset(7));
324 assert!(s.contains_offset(9));
325 assert!(!s.contains_offset(10), "end ist exklusiv");
326 assert!(!s.contains_offset(4));
327 }
328
329 #[test]
330 fn span_displays_as_start_dot_dot_end() {
331 assert_eq!(format!("{}", Span::new(3, 7)), "3..7");
332 }
333
334 #[test]
339 fn severity_orders_help_lower_than_error() {
340 assert!(Severity::Help < Severity::Note);
341 assert!(Severity::Note < Severity::Warning);
342 assert!(Severity::Warning < Severity::Error);
343 }
344
345 #[test]
346 fn severity_displays_lowercase_label() {
347 assert_eq!(format!("{}", Severity::Error), "error");
348 assert_eq!(format!("{}", Severity::Warning), "warning");
349 assert_eq!(format!("{}", Severity::Note), "note");
350 assert_eq!(format!("{}", Severity::Help), "help");
351 }
352
353 #[test]
358 fn diagnostic_error_constructor_sets_severity() {
359 let d = Diagnostic::error("oops", Span::new(0, 3));
360 assert_eq!(d.severity, Severity::Error);
361 assert_eq!(d.message, "oops");
362 assert_eq!(d.primary_span, Span::new(0, 3));
363 assert!(d.labels.is_empty());
364 }
365
366 #[test]
367 fn diagnostic_warning_constructor_sets_severity() {
368 let d = Diagnostic::warning("hmm", Span::new(2, 4));
369 assert_eq!(d.severity, Severity::Warning);
370 }
371
372 #[test]
373 fn diagnostic_with_label_appends_in_order() {
374 let d = Diagnostic::error("bad", Span::new(0, 3))
375 .with_label(Span::new(5, 10), "see here")
376 .with_label(Span::new(20, 22), "and here");
377 assert_eq!(d.labels.len(), 2);
378 assert_eq!(d.labels[0].message, "see here");
379 assert_eq!(d.labels[0].span, Span::new(5, 10));
380 assert_eq!(d.labels[1].message, "and here");
381 }
382
383 #[test]
388 fn parse_error_unexpected_token_to_diagnostic() {
389 let err = ParseError::UnexpectedToken {
390 found: TokenKind::Keyword("struct"),
391 expected: vec![TokenKind::Punct("{")],
392 span: Span::new(7, 13),
393 };
394 let d = err.to_diagnostic();
395 assert_eq!(d.severity, Severity::Error);
396 assert!(d.message.contains("unexpected token"));
397 assert!(d.message.contains("struct"));
398 assert!(d.message.contains("{"));
399 assert_eq!(d.primary_span, Span::new(7, 13));
400 }
401
402 #[test]
403 fn parse_error_unexpected_eof_to_diagnostic() {
404 let err = ParseError::UnexpectedEof {
405 expected: vec![TokenKind::Punct(";")],
406 span: Span::point(50),
407 };
408 let d = err.to_diagnostic();
409 assert!(d.message.contains("end of input"));
410 assert_eq!(d.primary_span, Span::point(50));
411 }
412
413 #[test]
414 fn parse_error_lexer_to_diagnostic_uses_message() {
415 let err = ParseError::LexerError {
416 message: "unterminated string literal".to_string(),
417 span: Span::new(10, 12),
418 };
419 let d = err.to_diagnostic();
420 assert_eq!(d.message, "unterminated string literal");
421 }
422
423 #[test]
424 fn parse_error_span_accessor_returns_inner_span() {
425 let err = ParseError::UnexpectedToken {
426 found: TokenKind::Ident,
427 expected: vec![],
428 span: Span::new(3, 6),
429 };
430 assert_eq!(err.span(), Span::new(3, 6));
431 }
432
433 #[test]
434 fn format_expected_handles_empty_single_and_many() {
435 assert_eq!(format_expected(&[]), "(nothing)");
436 assert_eq!(format_expected(&[TokenKind::Ident]), "Ident");
437 let many = format_expected(&[TokenKind::Punct(";"), TokenKind::Punct(",")]);
438 assert!(many.contains("one of"));
439 assert!(many.contains(';'));
440 assert!(many.contains(','));
441 }
442}