1use core::fmt;
16
17use crate::ast::Span;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum ParseError {
23 NonAscii {
29 span: Span,
31 },
32 ExpectedCrlf {
34 span: Span,
36 },
37 NumberTooLarge {
39 span: Span,
41 },
42 Expected {
44 what: &'static str,
46 span: Span,
48 },
49 UnexpectedEof {
51 span: Span,
53 },
54}
55
56impl ParseError {
57 #[must_use]
59 pub fn span(&self) -> Span {
60 match self {
61 Self::NonAscii { span }
62 | Self::ExpectedCrlf { span }
63 | Self::NumberTooLarge { span }
64 | Self::Expected { span, .. }
65 | Self::UnexpectedEof { span } => *span,
66 }
67 }
68}
69
70impl fmt::Display for ParseError {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::NonAscii { .. } => f.write_str(
74 "non-ASCII byte in grammar text; ABNF comments and prose values are ASCII-only",
75 ),
76 Self::ExpectedCrlf { .. } => f.write_str("expected CRLF line ending"),
77 Self::NumberTooLarge { .. } => f.write_str("numeric value does not fit in 64 bits"),
78 Self::Expected { what, .. } => write!(f, "expected {what}"),
79 Self::UnexpectedEof { .. } => f.write_str("unexpected end of grammar text"),
80 }
81 }
82}
83
84impl ParseError {
85 #[must_use]
90 pub fn render(&self, src: &str) -> String {
91 render_at(src, self.span(), &self.to_string())
92 }
93}
94
95impl std::error::Error for ParseError {}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
99#[non_exhaustive]
100pub enum CheckError {
101 DuplicateDefinition {
103 name: String,
105 span: Span,
107 },
108 IncrementalWithoutBase {
110 name: String,
112 shadows_core: bool,
114 span: Span,
116 },
117 UndefinedRule {
119 name: String,
121 span: Span,
123 },
124 InvalidRepeatRange {
126 min: u64,
128 max: u64,
130 span: Span,
132 },
133 InvalidNumericRange {
135 lo: u64,
137 hi: u64,
139 span: Span,
141 },
142 LeftRecursion {
149 cycle: Vec<String>,
151 },
152}
153
154impl fmt::Display for CheckError {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 match self {
157 Self::DuplicateDefinition { name, .. } => {
158 write!(f, "rule `{name}` is defined more than once")
159 }
160 Self::IncrementalWithoutBase {
161 name,
162 shadows_core: false,
163 ..
164 } => {
165 write!(f, "`{name} =/ …` has no prior `{name} = …`")
166 }
167 Self::IncrementalWithoutBase {
168 name,
169 shadows_core: true,
170 ..
171 } => write!(
172 f,
173 "`{name} =/ …` has no prior `{name} = …`; `{name}` is an implicit core rule and \
174 cannot be extended in place — write `{name} = <core definition> / …` to shadow \
175 and extend it"
176 ),
177 Self::UndefinedRule { name, .. } => write!(f, "undefined rule `{name}`"),
178 Self::InvalidRepeatRange { min, max, .. } => {
179 write!(f, "repetition lower bound {min} exceeds upper bound {max}")
180 }
181 Self::InvalidNumericRange { lo, hi, .. } => {
182 write!(
183 f,
184 "numeric range starts at {lo:#X} and ends below it at {hi:#X}"
185 )
186 }
187 Self::LeftRecursion { cycle } => {
188 write!(f, "left recursion: {}", cycle.join(" -> "))
189 }
190 }
191 }
192}
193
194impl CheckError {
195 #[must_use]
200 pub fn span(&self) -> Option<Span> {
201 match self {
202 Self::DuplicateDefinition { span, .. }
203 | Self::IncrementalWithoutBase { span, .. }
204 | Self::UndefinedRule { span, .. }
205 | Self::InvalidRepeatRange { span, .. }
206 | Self::InvalidNumericRange { span, .. } => Some(*span),
207 Self::LeftRecursion { .. } => None,
208 }
209 }
210
211 #[must_use]
213 pub fn render(&self, src: &str) -> String {
214 match self.span() {
215 Some(span) => render_at(src, span, &self.to_string()),
216 None => self.to_string(),
217 }
218 }
219}
220
221impl std::error::Error for CheckError {}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
249pub struct CheckErrors(Vec<CheckError>);
250
251impl CheckErrors {
252 #[must_use]
254 pub fn as_slice(&self) -> &[CheckError] {
255 &self.0
256 }
257
258 #[must_use]
260 pub fn into_vec(self) -> Vec<CheckError> {
261 self.0
262 }
263
264 #[must_use]
269 pub fn render(&self, src: &str) -> String {
270 let mut out = String::new();
271 for error in &self.0 {
272 out.push_str(&error.render(src));
273 out.push('\n');
274 }
275 let count = self.0.len();
276 out.push_str(&format!(
277 "{count} structural {}",
278 if count == 1 { "error" } else { "errors" }
279 ));
280 out
281 }
282}
283
284impl core::ops::Deref for CheckErrors {
285 type Target = [CheckError];
286
287 fn deref(&self) -> &Self::Target {
288 &self.0
289 }
290}
291
292impl From<Vec<CheckError>> for CheckErrors {
293 fn from(errors: Vec<CheckError>) -> Self {
294 Self(errors)
295 }
296}
297
298impl From<CheckErrors> for Vec<CheckError> {
299 fn from(errors: CheckErrors) -> Self {
300 errors.0
301 }
302}
303
304impl IntoIterator for CheckErrors {
305 type Item = CheckError;
306 type IntoIter = std::vec::IntoIter<CheckError>;
307
308 fn into_iter(self) -> Self::IntoIter {
309 self.0.into_iter()
310 }
311}
312
313impl<'a> IntoIterator for &'a CheckErrors {
314 type Item = &'a CheckError;
315 type IntoIter = core::slice::Iter<'a, CheckError>;
316
317 fn into_iter(self) -> Self::IntoIter {
318 self.0.iter()
319 }
320}
321
322impl fmt::Display for CheckErrors {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 match self.0.as_slice() {
329 [] => f.write_str("no structural errors"),
330 [only] => write!(f, "{only}"),
331 many => {
332 write!(f, "{} structural errors:", many.len())?;
333 for error in many {
334 write!(f, "\n {error}")?;
335 }
336 Ok(())
337 }
338 }
339 }
340}
341
342impl std::error::Error for CheckErrors {}
343
344#[derive(Clone, Debug, PartialEq, Eq)]
346#[non_exhaustive]
347pub enum LintWarning {
348 UnreferencedRule {
354 name: String,
356 },
357 UnreachableRule {
363 name: String,
365 },
366 UnproductiveRule {
368 name: String,
370 },
371 UnproductiveAlternative {
373 rule: String,
375 branch: u32,
377 },
378 ShadowsCoreRule {
382 name: String,
384 },
385}
386
387impl fmt::Display for LintWarning {
388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 match self {
390 Self::UnreferencedRule { name } => {
391 write!(f, "rule `{name}` is never referenced")
392 }
393 Self::UnreachableRule { name } => {
394 write!(
395 f,
396 "rule `{name}` is not reachable from the given start rules"
397 )
398 }
399 Self::UnproductiveRule { name } => {
400 write!(
401 f,
402 "rule `{name}` can never match: it has no finite expansion"
403 )
404 }
405 Self::UnproductiveAlternative { rule, branch } => write!(
406 f,
407 "branch {branch} of an alternation in rule `{rule}` can never match"
408 ),
409 Self::ShadowsCoreRule { name } => {
410 write!(f, "rule `{name}` shadows the core rule of the same name")
411 }
412 }
413 }
414}
415
416#[derive(Clone, Debug, PartialEq, Eq)]
420#[non_exhaustive]
421pub enum MatchError {
422 ProseValueReachable {
424 rule: String,
426 prose: String,
428 },
429 UnrepresentableTerminal {
431 rule: String,
433 terminal: String,
435 },
436 UnknownRule(
438 String,
440 ),
441 LeftRecursionDetected {
444 rule: String,
446 },
447 StepLimit,
449 DepthLimit,
456}
457
458impl fmt::Display for MatchError {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 match self {
461 Self::ProseValueReachable { rule, prose } => write!(
462 f,
463 "rule `{rule}` can reach the prose value <{prose}>, which has no matching semantics"
464 ),
465 Self::UnrepresentableTerminal { rule, terminal } => write!(
466 f,
467 "rule `{rule}` can reach terminal {terminal}, which matches no Unicode scalar value"
468 ),
469 Self::UnknownRule(name) => write!(f, "unknown rule `{name}`"),
470 Self::LeftRecursionDetected { rule } => {
471 write!(f, "left recursion detected at rule `{rule}`")
472 }
473 Self::StepLimit => f.write_str("step limit exceeded"),
474 Self::DepthLimit => f.write_str(
475 "recursion depth limit exceeded: the input nests deeper than `MatchOptions::max_depth` allows",
476 ),
477 }
478 }
479}
480
481impl std::error::Error for MatchError {}
482
483#[derive(Clone, Debug, PartialEq, Eq)]
485#[non_exhaustive]
486pub enum GenError {
487 ProseValueReachable {
489 rule: String,
491 prose: String,
493 },
494 UnrepresentableTerminal {
496 rule: String,
498 terminal: String,
500 },
501 UnknownRule(
503 String,
505 ),
506 NoFiniteExpansion {
508 rule: String,
510 },
511 OutputLimit,
517 StepLimit,
519}
520
521impl fmt::Display for GenError {
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 match self {
524 Self::ProseValueReachable { rule, prose } => write!(
525 f,
526 "rule `{rule}` can reach the prose value <{prose}>, which cannot be generated"
527 ),
528 Self::UnrepresentableTerminal { rule, terminal } => write!(
529 f,
530 "rule `{rule}` can reach terminal {terminal}, which has no representable value"
531 ),
532 Self::UnknownRule(name) => write!(f, "unknown rule `{name}`"),
533 Self::NoFiniteExpansion { rule } => {
534 write!(f, "rule `{rule}` has no finite expansion")
535 }
536 Self::OutputLimit => f.write_str("output length limit exceeded"),
537 Self::StepLimit => f.write_str("step limit exceeded"),
538 }
539 }
540}
541
542impl std::error::Error for GenError {}
543
544fn render_at(src: &str, span: Span, message: &str) -> String {
550 let offset = (span.start as usize).min(src.len());
551 let before = &src[..offset];
552 let line_number = before.matches('\n').count() + 1;
553 let line_start = before.rfind('\n').map_or(0, |index| index + 1);
554 let line = src[line_start..].lines().next().unwrap_or_default();
556 let column = offset - line_start;
557 let width = (span.end.saturating_sub(span.start) as usize)
560 .min(line.len().saturating_sub(column))
561 .max(1);
562 format!(
563 "line {line_number}, column {}: {message}\n {line}\n {}{}",
564 column + 1,
565 " ".repeat(column),
566 "^".repeat(width)
567 )
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn incremental_without_base_suggests_the_workaround_for_core_rules() {
576 let plain = CheckError::IncrementalWithoutBase {
577 name: "foo".into(),
578 shadows_core: false,
579 span: Span::default(),
580 };
581 let core = CheckError::IncrementalWithoutBase {
582 name: "DIGIT".into(),
583 shadows_core: true,
584 span: Span::default(),
585 };
586 assert!(!plain.to_string().contains("shadow"));
587 assert!(core.to_string().contains("DIGIT = <core definition> / …"));
588 }
589
590 #[test]
591 fn parse_error_span_is_reported_for_every_variant() {
592 let span = Span::new(3, 7);
593 for error in [
594 ParseError::NonAscii { span },
595 ParseError::ExpectedCrlf { span },
596 ParseError::NumberTooLarge { span },
597 ParseError::Expected { what: "=", span },
598 ParseError::UnexpectedEof { span },
599 ] {
600 assert_eq!(error.span(), span);
601 assert!(!error.to_string().is_empty());
602 }
603 }
604
605 fn check_errors(src: &str) -> CheckErrors {
608 crate::ast::Grammar::parse(src)
609 .expect("parses")
610 .check()
611 .expect_err("should not check")
612 }
613
614 #[test]
615 fn check_errors_is_a_std_error() {
616 fn takes_std_error(_: &dyn std::error::Error) {}
619 takes_std_error(&check_errors("start = missing\r\n"));
620 }
621
622 #[test]
623 fn check_errors_behaves_like_a_slice() {
624 let errors = check_errors("start = a b c\r\n");
625 assert_eq!(errors.len(), 3);
626 assert!(!errors.is_empty());
627 assert_eq!(errors.iter().count(), 3);
628 assert!(matches!(errors[0], CheckError::UndefinedRule { .. }));
629 assert_eq!(errors.as_slice().len(), 3);
630 }
631
632 #[test]
633 fn check_errors_iterates_by_value_and_by_reference() {
634 let errors = check_errors("start = a b\r\n");
635 assert_eq!((&errors).into_iter().count(), 2);
636 assert_eq!(errors.into_iter().count(), 2);
637 }
638
639 #[test]
640 fn check_errors_converts_both_ways() {
641 let errors = check_errors("start = a b\r\n");
642 let as_vec: Vec<CheckError> = errors.clone().into();
643 assert_eq!(as_vec.len(), 2);
644 assert_eq!(CheckErrors::from(as_vec.clone()), errors);
645 assert_eq!(errors.into_vec(), as_vec);
646 }
647
648 #[test]
649 fn one_error_displays_as_itself() {
650 let errors = check_errors("start = missing\r\n");
651 assert_eq!(errors.to_string(), errors[0].to_string());
652 assert!(!errors.to_string().contains("structural errors"));
653 }
654
655 #[test]
656 fn several_errors_are_counted_then_listed() {
657 let errors = check_errors("start = a b c\r\n");
658 let shown = errors.to_string();
659 assert!(shown.starts_with("3 structural errors:"), "{shown}");
660 for error in &errors {
661 assert!(shown.contains(&error.to_string()), "{shown}");
662 }
663 }
664
665 #[test]
666 fn render_shows_every_error_against_the_source() {
667 let src = "start = a b c d\r\n";
669 let rendered = check_errors(src).render(src);
670
671 assert_eq!(rendered.matches("undefined rule").count(), 4);
672 assert!(rendered.contains('^'), "with carets: {rendered}");
673 assert!(rendered.ends_with("4 structural errors"), "{rendered}");
674 }
675
676 #[test]
677 fn render_says_error_in_the_singular() {
678 let src = "start = missing\r\n";
679 assert!(
680 check_errors(src)
681 .render(src)
682 .ends_with("1 structural error")
683 );
684 }
685}