1pub use crate::error_impl::{ParseError, ParseErrorKind};
6
7use super::functions::*;
8
9#[allow(dead_code)]
11#[allow(missing_docs)]
12pub struct ErrorExplanation {
13 pub code: String,
14 pub title: String,
15 pub description: String,
16 pub example_bad: String,
17 #[allow(missing_docs)]
18 pub example_good: String,
19}
20impl ErrorExplanation {
21 #[allow(dead_code)]
22 #[allow(missing_docs)]
23 pub fn new(
24 code: impl Into<String>,
25 title: impl Into<String>,
26 desc: impl Into<String>,
27 bad: impl Into<String>,
28 good: impl Into<String>,
29 ) -> Self {
30 Self {
31 code: code.into(),
32 title: title.into(),
33 description: desc.into(),
34 example_bad: bad.into(),
35 example_good: good.into(),
36 }
37 }
38 #[allow(dead_code)]
39 #[allow(missing_docs)]
40 pub fn render(&self) -> String {
41 format!(
42 "[{}] {}\n\n{}\n\nBad:\n{}\n\nGood:\n{}",
43 self.code, self.title, self.description, self.example_bad, self.example_good
44 )
45 }
46}
47#[allow(dead_code)]
49#[allow(missing_docs)]
50pub struct ErrorLocationResolver {
51 source: String,
52 line_starts: Vec<usize>,
53}
54impl ErrorLocationResolver {
55 #[allow(dead_code)]
56 #[allow(missing_docs)]
57 pub fn new(source: impl Into<String>) -> Self {
58 let s = source.into();
59 let mut starts = vec![0usize];
60 for (i, c) in s.char_indices() {
61 if c == '\n' {
62 starts.push(i + 1);
63 }
64 }
65 Self {
66 source: s,
67 line_starts: starts,
68 }
69 }
70 #[allow(dead_code)]
71 #[allow(missing_docs)]
72 pub fn resolve(&self, byte_offset: usize) -> (usize, usize) {
73 let off = byte_offset.min(self.source.len());
74 let line = self
75 .line_starts
76 .partition_point(|&s| s <= off)
77 .saturating_sub(1);
78 let col = off - self.line_starts[line];
79 (line, col)
80 }
81 #[allow(dead_code)]
82 #[allow(missing_docs)]
83 pub fn line_text(&self, line: usize) -> &str {
84 let start = *self.line_starts.get(line).unwrap_or(&self.source.len());
85 let end = *self.line_starts.get(line + 1).unwrap_or(&self.source.len());
86 let end = if end > start && self.source.as_bytes().get(end - 1) == Some(&b'\n') {
87 end - 1
88 } else {
89 end
90 };
91 &self.source[start..end]
92 }
93 #[allow(dead_code)]
94 #[allow(missing_docs)]
95 pub fn line_count(&self) -> usize {
96 self.line_starts.len()
97 }
98 #[allow(dead_code)]
99 #[allow(missing_docs)]
100 pub fn snippet(&self, byte_offset: usize, context_lines: usize) -> String {
101 let (line, col) = self.resolve(byte_offset);
102 let start_line = line.saturating_sub(context_lines);
103 let end_line = (line + context_lines).min(self.line_count().saturating_sub(1));
104 let mut out = String::new();
105 for l in start_line..=end_line {
106 let text = self.line_text(l);
107 out.push_str(&format!("{:4} | {}\n", l + 1, text));
108 if l == line {
109 out.push_str(&format!(" | {}^\n", " ".repeat(col)));
110 }
111 }
112 out
113 }
114}
115#[allow(dead_code)]
117#[allow(missing_docs)]
118pub struct StringErrorSink {
119 buffer: String,
120 count: usize,
121}
122impl StringErrorSink {
123 #[allow(dead_code)]
124 #[allow(missing_docs)]
125 pub fn new() -> Self {
126 Self {
127 buffer: String::new(),
128 count: 0,
129 }
130 }
131 #[allow(dead_code)]
132 #[allow(missing_docs)]
133 pub fn emit(&mut self, e: &RichError) {
134 if !self.buffer.is_empty() {
135 self.buffer.push('\n');
136 }
137 self.buffer.push_str(&e.format());
138 self.count += 1;
139 }
140 #[allow(dead_code)]
141 #[allow(missing_docs)]
142 pub fn contents(&self) -> &str {
143 &self.buffer
144 }
145 #[allow(dead_code)]
146 #[allow(missing_docs)]
147 pub fn count(&self) -> usize {
148 self.count
149 }
150 #[allow(dead_code)]
151 #[allow(missing_docs)]
152 pub fn clear(&mut self) {
153 self.buffer.clear();
154 self.count = 0;
155 }
156}
157#[allow(dead_code)]
159#[allow(missing_docs)]
160#[derive(Clone, Debug)]
161pub struct ParseErrorContext {
162 pub error: ParseError,
164 pub decl_name: Option<String>,
166 #[allow(missing_docs)]
168 pub phase: Option<String>,
169}
170impl ParseErrorContext {
171 #[allow(dead_code)]
173 #[allow(missing_docs)]
174 pub fn new(error: ParseError) -> Self {
175 Self {
176 error,
177 decl_name: None,
178 phase: None,
179 }
180 }
181 #[allow(dead_code)]
183 #[allow(missing_docs)]
184 pub fn with_decl(mut self, name: &str) -> Self {
185 self.decl_name = Some(name.to_string());
186 self
187 }
188 #[allow(dead_code)]
190 #[allow(missing_docs)]
191 pub fn with_phase(mut self, phase: &str) -> Self {
192 self.phase = Some(phase.to_string());
193 self
194 }
195}
196#[allow(dead_code)]
198#[allow(missing_docs)]
199#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
200pub enum ErrorSeverityLevel {
201 Hint,
202 Note,
203 Warning,
204 Error,
205 Fatal,
206}
207#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
209#[allow(missing_docs)]
210pub enum ErrorSeverity {
211 Note,
213 Warning,
215 Error,
217}
218impl ErrorSeverity {
219 #[allow(missing_docs)]
221 pub fn is_error(&self) -> bool {
222 matches!(self, ErrorSeverity::Error)
223 }
224 #[allow(missing_docs)]
226 pub fn is_recoverable(&self) -> bool {
227 !self.is_error()
228 }
229}
230#[derive(Clone, Debug, Default)]
232#[allow(missing_docs)]
233pub struct ParseErrorCollector {
234 errors: Vec<ParseError>,
236 limit: usize,
238}
239impl ParseErrorCollector {
240 #[allow(missing_docs)]
242 pub fn new() -> Self {
243 Self::default()
244 }
245 #[allow(missing_docs)]
247 pub fn with_limit(limit: usize) -> Self {
248 Self {
249 errors: Vec::new(),
250 limit,
251 }
252 }
253 #[allow(missing_docs)]
255 pub fn add(&mut self, error: ParseError) {
256 if self.limit == 0 || self.errors.len() < self.limit {
257 self.errors.push(error);
258 }
259 }
260 #[allow(missing_docs)]
262 pub fn has_errors(&self) -> bool {
263 !self.errors.is_empty()
264 }
265 #[allow(missing_docs)]
267 pub fn len(&self) -> usize {
268 self.errors.len()
269 }
270 #[allow(missing_docs)]
272 pub fn is_empty(&self) -> bool {
273 self.errors.is_empty()
274 }
275 #[allow(missing_docs)]
277 pub fn into_errors(self) -> Vec<ParseError> {
278 self.errors
279 }
280 #[allow(missing_docs)]
282 pub fn errors(&self) -> &[ParseError] {
283 &self.errors
284 }
285 #[allow(missing_docs)]
287 pub fn first_error(&self) -> Option<&ParseError> {
288 self.errors.first()
289 }
290 #[allow(missing_docs)]
292 pub fn clear(&mut self) {
293 self.errors.clear();
294 }
295 #[allow(missing_docs)]
297 pub fn is_full(&self) -> bool {
298 self.limit > 0 && self.errors.len() >= self.limit
299 }
300 #[allow(missing_docs)]
302 pub fn merge(&mut self, other: ParseErrorCollector) {
303 for e in other.errors {
304 self.add(e);
305 }
306 }
307}
308#[allow(dead_code)]
310#[allow(missing_docs)]
311pub struct BatchErrorReport {
312 pub errors: Vec<RichError>,
313 pub source_file: String,
314 pub parse_time_us: u64,
315}
316impl BatchErrorReport {
317 #[allow(dead_code)]
318 #[allow(missing_docs)]
319 pub fn new(source_file: impl Into<String>, errors: Vec<RichError>, parse_time_us: u64) -> Self {
320 Self {
321 errors,
322 source_file: source_file.into(),
323 parse_time_us,
324 }
325 }
326 #[allow(dead_code)]
327 #[allow(missing_docs)]
328 pub fn error_count(&self) -> usize {
329 self.errors
330 .iter()
331 .filter(|e| e.severity >= ErrorSeverityLevel::Error)
332 .count()
333 }
334 #[allow(dead_code)]
335 #[allow(missing_docs)]
336 pub fn warning_count(&self) -> usize {
337 self.errors
338 .iter()
339 .filter(|e| e.severity == ErrorSeverityLevel::Warning)
340 .count()
341 }
342 #[allow(dead_code)]
343 #[allow(missing_docs)]
344 pub fn is_success(&self) -> bool {
345 self.error_count() == 0
346 }
347 #[allow(dead_code)]
348 #[allow(missing_docs)]
349 pub fn summary_line(&self) -> String {
350 format!(
351 "{}: {} error(s), {} warning(s) in {}us",
352 self.source_file,
353 self.error_count(),
354 self.warning_count(),
355 self.parse_time_us
356 )
357 }
358}
359#[allow(dead_code)]
361#[allow(missing_docs)]
362pub struct ErrorGrouper {
363 groups: std::collections::HashMap<String, Vec<RichError>>,
364}
365impl ErrorGrouper {
366 #[allow(dead_code)]
367 #[allow(missing_docs)]
368 pub fn new() -> Self {
369 Self {
370 groups: std::collections::HashMap::new(),
371 }
372 }
373 #[allow(dead_code)]
374 #[allow(missing_docs)]
375 pub fn add(&mut self, e: RichError) {
376 let key = e.code.clone().unwrap_or_else(|| "no-code".to_string());
377 self.groups.entry(key).or_default().push(e);
378 }
379 #[allow(dead_code)]
380 #[allow(missing_docs)]
381 pub fn group_count(&self) -> usize {
382 self.groups.len()
383 }
384 #[allow(dead_code)]
385 #[allow(missing_docs)]
386 pub fn errors_in_group(&self, code: &str) -> &[RichError] {
387 self.groups.get(code).map(|v| v.as_slice()).unwrap_or(&[])
388 }
389 #[allow(dead_code)]
390 #[allow(missing_docs)]
391 pub fn most_common_code(&self) -> Option<&str> {
392 self.groups
393 .iter()
394 .max_by_key(|(_, v)| v.len())
395 .map(|(k, _)| k.as_str())
396 }
397 #[allow(dead_code)]
398 #[allow(missing_docs)]
399 pub fn total_error_count(&self) -> usize {
400 self.groups.values().map(|v| v.len()).sum()
401 }
402}
403#[allow(dead_code)]
404#[allow(missing_docs)]
405#[derive(Clone, Copy, Debug, PartialEq, Eq)]
406pub enum RecoveryHintKind {
407 InsertBefore,
408 InsertAfter,
409 Delete,
410 Replace,
411 Reorder,
412}
413#[allow(dead_code)]
415#[allow(missing_docs)]
416#[derive(Default, Debug)]
417pub struct ErrorRateTracker {
418 window_size: usize,
419 counts: std::collections::VecDeque<usize>,
420 current: usize,
421}
422impl ErrorRateTracker {
423 #[allow(dead_code)]
424 #[allow(missing_docs)]
425 pub fn new(window_size: usize) -> Self {
426 Self {
427 window_size,
428 counts: std::collections::VecDeque::new(),
429 current: 0,
430 }
431 }
432 #[allow(dead_code)]
433 #[allow(missing_docs)]
434 pub fn record(&mut self, errors: usize) {
435 self.current += errors;
436 }
437 #[allow(dead_code)]
438 #[allow(missing_docs)]
439 pub fn commit_window(&mut self) {
440 self.counts.push_back(self.current);
441 if self.counts.len() > self.window_size {
442 self.counts.pop_front();
443 }
444 self.current = 0;
445 }
446 #[allow(dead_code)]
447 #[allow(missing_docs)]
448 pub fn average(&self) -> f64 {
449 if self.counts.is_empty() {
450 return 0.0;
451 }
452 self.counts.iter().sum::<usize>() as f64 / self.counts.len() as f64
453 }
454 #[allow(dead_code)]
455 #[allow(missing_docs)]
456 pub fn trend(&self) -> f64 {
457 if self.counts.len() < 2 {
458 return 0.0;
459 }
460 let first = *self
461 .counts
462 .front()
463 .expect("counts.len() >= 2 per check above") as f64;
464 let last = *self
465 .counts
466 .back()
467 .expect("counts.len() >= 2 per check above") as f64;
468 last - first
469 }
470}
471#[derive(Clone, Debug)]
473#[allow(missing_docs)]
474pub struct ParseWarning {
475 pub message: String,
477 pub line: u32,
479 #[allow(missing_docs)]
481 pub col: u32,
482}
483impl ParseWarning {
484 #[allow(missing_docs)]
486 pub fn new(msg: &str, line: u32, col: u32) -> Self {
487 Self {
488 message: msg.to_string(),
489 line,
490 col,
491 }
492 }
493}
494#[derive(Clone, Debug)]
496#[allow(missing_docs)]
497pub struct ParseDiagnostic {
498 pub severity: ErrorSeverity,
500 pub filename: String,
502 #[allow(missing_docs)]
504 pub line: u32,
505 pub col: u32,
507 pub message: String,
509 #[allow(missing_docs)]
511 pub hint: Option<String>,
512 pub code: Option<String>,
514}
515impl ParseDiagnostic {
516 #[allow(missing_docs)]
518 pub fn new(
519 severity: ErrorSeverity,
520 filename: &str,
521 line: u32,
522 col: u32,
523 message: &str,
524 ) -> Self {
525 Self {
526 severity,
527 filename: filename.to_string(),
528 line,
529 col,
530 message: message.to_string(),
531 hint: None,
532 code: None,
533 }
534 }
535 #[allow(missing_docs)]
537 pub fn error(filename: &str, line: u32, col: u32, msg: &str) -> Self {
538 Self::new(ErrorSeverity::Error, filename, line, col, msg)
539 }
540 #[allow(missing_docs)]
542 pub fn warning(filename: &str, line: u32, col: u32, msg: &str) -> Self {
543 Self::new(ErrorSeverity::Warning, filename, line, col, msg)
544 }
545 #[allow(missing_docs)]
547 pub fn with_hint(mut self, hint: &str) -> Self {
548 self.hint = Some(hint.to_string());
549 self
550 }
551 #[allow(missing_docs)]
553 pub fn with_code(mut self, code: &str) -> Self {
554 self.code = Some(code.to_string());
555 self
556 }
557 #[allow(missing_docs)]
559 pub fn is_error(&self) -> bool {
560 self.severity.is_error()
561 }
562}
563#[allow(dead_code)]
565#[allow(missing_docs)]
566pub struct ErrorExplanationBook {
567 pages: std::collections::HashMap<String, ErrorExplanation>,
568}
569impl ErrorExplanationBook {
570 #[allow(dead_code)]
571 #[allow(missing_docs)]
572 pub fn new() -> Self {
573 Self {
574 pages: std::collections::HashMap::new(),
575 }
576 }
577 #[allow(dead_code)]
578 #[allow(missing_docs)]
579 pub fn add(&mut self, page: ErrorExplanation) {
580 self.pages.insert(page.code.clone(), page);
581 }
582 #[allow(dead_code)]
583 #[allow(missing_docs)]
584 pub fn lookup(&self, code: &str) -> Option<&ErrorExplanation> {
585 self.pages.get(code)
586 }
587 #[allow(dead_code)]
588 #[allow(missing_docs)]
589 pub fn count(&self) -> usize {
590 self.pages.len()
591 }
592}
593#[derive(Clone, Debug, Default)]
595#[allow(missing_docs)]
596pub struct ParseErrorGroup {
597 pub label: String,
599 pub errors: Vec<ParseError>,
601}
602impl ParseErrorGroup {
603 #[allow(missing_docs)]
605 pub fn new(label: &str) -> Self {
606 Self {
607 label: label.to_string(),
608 errors: Vec::new(),
609 }
610 }
611 #[allow(missing_docs)]
613 pub fn add(&mut self, e: ParseError) {
614 self.errors.push(e);
615 }
616 #[allow(missing_docs)]
618 pub fn len(&self) -> usize {
619 self.errors.len()
620 }
621 #[allow(missing_docs)]
623 pub fn is_empty(&self) -> bool {
624 self.errors.is_empty()
625 }
626}
627#[allow(dead_code)]
629#[allow(missing_docs)]
630#[derive(Clone, Debug)]
631pub struct RecoveryHint {
632 pub kind: RecoveryHintKind,
633 pub text: String,
634}
635impl RecoveryHint {
636 #[allow(dead_code)]
637 #[allow(missing_docs)]
638 pub fn insert_before(text: impl Into<String>) -> Self {
639 Self {
640 kind: RecoveryHintKind::InsertBefore,
641 text: text.into(),
642 }
643 }
644 #[allow(dead_code)]
645 #[allow(missing_docs)]
646 pub fn delete(text: impl Into<String>) -> Self {
647 Self {
648 kind: RecoveryHintKind::Delete,
649 text: text.into(),
650 }
651 }
652 #[allow(dead_code)]
653 #[allow(missing_docs)]
654 pub fn replace(text: impl Into<String>) -> Self {
655 Self {
656 kind: RecoveryHintKind::Replace,
657 text: text.into(),
658 }
659 }
660 #[allow(dead_code)]
661 #[allow(missing_docs)]
662 pub fn description(&self) -> String {
663 match self.kind {
664 RecoveryHintKind::InsertBefore => {
665 format!("insert '{}' before this token", self.text)
666 }
667 RecoveryHintKind::InsertAfter => {
668 format!("insert '{}' after this token", self.text)
669 }
670 RecoveryHintKind::Delete => format!("delete '{}'", self.text),
671 RecoveryHintKind::Replace => format!("replace with '{}'", self.text),
672 RecoveryHintKind::Reorder => format!("reorder: {}", self.text),
673 }
674 }
675}
676#[allow(dead_code)]
678#[allow(missing_docs)]
679#[derive(Clone, Debug)]
680pub struct TaggedError {
681 pub inner: RichError,
682 pub tags: Vec<ErrorTag>,
683 pub hints: Vec<RecoveryHint>,
684}
685impl TaggedError {
686 #[allow(dead_code)]
687 #[allow(missing_docs)]
688 pub fn new(e: RichError) -> Self {
689 Self {
690 inner: e,
691 tags: Vec::new(),
692 hints: Vec::new(),
693 }
694 }
695 #[allow(dead_code)]
696 #[allow(missing_docs)]
697 pub fn with_tag(mut self, tag: ErrorTag) -> Self {
698 self.tags.push(tag);
699 self
700 }
701 #[allow(dead_code)]
702 #[allow(missing_docs)]
703 pub fn with_hint(mut self, hint: RecoveryHint) -> Self {
704 self.hints.push(hint);
705 self
706 }
707 #[allow(dead_code)]
708 #[allow(missing_docs)]
709 pub fn has_tag(&self, tag: ErrorTag) -> bool {
710 self.tags.contains(&tag)
711 }
712 #[allow(dead_code)]
713 #[allow(missing_docs)]
714 pub fn format_full(&self) -> String {
715 let mut out = self.inner.format();
716 for hint in &self.hints {
717 out.push_str(&format!("\n help: {}", hint.description()));
718 }
719 out
720 }
721}
722#[allow(dead_code)]
724#[allow(missing_docs)]
725#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
726pub enum ErrorTag {
727 Syntax,
728 Type,
729 Name,
730 Import,
731 Layout,
732 Unicode,
733 Overflow,
734 Internal,
735}
736#[allow(dead_code)]
738#[allow(missing_docs)]
739pub struct ErrorFilter {
740 suppressed_codes: std::collections::HashSet<String>,
741 min_severity: ErrorSeverityLevel,
742}
743impl ErrorFilter {
744 #[allow(dead_code)]
745 #[allow(missing_docs)]
746 pub fn new(min_severity: ErrorSeverityLevel) -> Self {
747 Self {
748 suppressed_codes: std::collections::HashSet::new(),
749 min_severity,
750 }
751 }
752 #[allow(dead_code)]
753 #[allow(missing_docs)]
754 pub fn suppress_code(&mut self, code: impl Into<String>) {
755 self.suppressed_codes.insert(code.into());
756 }
757 #[allow(dead_code)]
758 #[allow(missing_docs)]
759 pub fn should_show(&self, e: &RichError) -> bool {
760 if e.severity < self.min_severity {
761 return false;
762 }
763 if let Some(code) = &e.code {
764 if self.suppressed_codes.contains(code.as_str()) {
765 return false;
766 }
767 }
768 true
769 }
770 #[allow(dead_code)]
771 #[allow(missing_docs)]
772 pub fn filter<'a>(&self, errors: &'a [RichError]) -> Vec<&'a RichError> {
773 errors.iter().filter(|e| self.should_show(e)).collect()
774 }
775}
776#[derive(Clone, Copy, Debug, PartialEq, Eq)]
778#[allow(missing_docs)]
779pub enum RecoveryStrategy {
780 Abort,
782 SkipToSync,
784 InsertToken,
786 Replace,
788}
789impl RecoveryStrategy {
790 #[allow(missing_docs)]
792 pub fn continues(&self) -> bool {
793 !matches!(self, RecoveryStrategy::Abort)
794 }
795}
796#[derive(Clone, Debug, Default)]
798#[allow(missing_docs)]
799pub struct ParseErrorStats {
800 pub total: u64,
802 pub eof_errors: u64,
804 #[allow(missing_docs)]
806 pub located_errors: u64,
807}
808impl ParseErrorStats {
809 #[allow(missing_docs)]
811 pub fn new() -> Self {
812 Self::default()
813 }
814 #[allow(missing_docs)]
816 pub fn record(&mut self, err: &ParseError) {
817 self.total += 1;
818 if err.span.line == 0 {
819 self.eof_errors += 1;
820 } else {
821 self.located_errors += 1;
822 }
823 }
824 #[allow(missing_docs)]
826 pub fn has_errors(&self) -> bool {
827 self.total > 0
828 }
829}
830#[allow(dead_code)]
832#[allow(missing_docs)]
833pub struct ErrorBudget {
834 budget: usize,
835 spent: usize,
836}
837impl ErrorBudget {
838 #[allow(dead_code)]
839 #[allow(missing_docs)]
840 pub fn new(budget: usize) -> Self {
841 Self { budget, spent: 0 }
842 }
843 #[allow(dead_code)]
844 #[allow(missing_docs)]
845 pub fn spend(&mut self) -> bool {
846 if self.spent < self.budget {
847 self.spent += 1;
848 true
849 } else {
850 false
851 }
852 }
853 #[allow(dead_code)]
854 #[allow(missing_docs)]
855 pub fn remaining(&self) -> usize {
856 self.budget.saturating_sub(self.spent)
857 }
858 #[allow(dead_code)]
859 #[allow(missing_docs)]
860 pub fn is_exhausted(&self) -> bool {
861 self.spent >= self.budget
862 }
863 #[allow(dead_code)]
864 #[allow(missing_docs)]
865 pub fn fraction_used(&self) -> f64 {
866 if self.budget == 0 {
867 1.0
868 } else {
869 self.spent as f64 / self.budget as f64
870 }
871 }
872}
873#[allow(dead_code)]
875#[allow(missing_docs)]
876pub struct ErrorAccumulator2 {
877 errors: Vec<RichError>,
878 max_errors: usize,
879 fatal_encountered: bool,
880}
881impl ErrorAccumulator2 {
882 #[allow(dead_code)]
883 #[allow(missing_docs)]
884 pub fn new(max_errors: usize) -> Self {
885 Self {
886 errors: Vec::new(),
887 max_errors,
888 fatal_encountered: false,
889 }
890 }
891 #[allow(dead_code)]
892 #[allow(missing_docs)]
893 pub fn add(&mut self, e: RichError) -> bool {
894 if self.errors.len() >= self.max_errors {
895 return false;
896 }
897 if e.is_fatal() {
898 self.fatal_encountered = true;
899 }
900 self.errors.push(e);
901 true
902 }
903 #[allow(dead_code)]
904 #[allow(missing_docs)]
905 pub fn error_count(&self) -> usize {
906 self.errors
907 .iter()
908 .filter(|e| e.severity >= ErrorSeverityLevel::Error)
909 .count()
910 }
911 #[allow(dead_code)]
912 #[allow(missing_docs)]
913 pub fn warning_count(&self) -> usize {
914 self.errors
915 .iter()
916 .filter(|e| e.severity == ErrorSeverityLevel::Warning)
917 .count()
918 }
919 #[allow(dead_code)]
920 #[allow(missing_docs)]
921 pub fn has_fatal(&self) -> bool {
922 self.fatal_encountered
923 }
924 #[allow(dead_code)]
925 #[allow(missing_docs)]
926 pub fn is_clean(&self) -> bool {
927 self.errors.is_empty()
928 }
929 #[allow(dead_code)]
930 #[allow(missing_docs)]
931 pub fn all_errors(&self) -> &[RichError] {
932 &self.errors
933 }
934 #[allow(dead_code)]
935 #[allow(missing_docs)]
936 pub fn format_all(&self) -> String {
937 self.errors
938 .iter()
939 .map(|e| e.format())
940 .collect::<Vec<_>>()
941 .join("\n")
942 }
943 #[allow(dead_code)]
944 #[allow(missing_docs)]
945 pub fn sorted_by_severity(&self) -> Vec<&RichError> {
946 let mut sorted: Vec<_> = self.errors.iter().collect();
947 sorted.sort_by(|a, b| b.severity.cmp(&a.severity));
948 sorted
949 }
950}
951#[allow(dead_code)]
953#[allow(missing_docs)]
954#[derive(Clone, Debug)]
955pub struct RichError {
956 pub message: String,
957 pub severity: ErrorSeverityLevel,
958 pub code: Option<String>,
959 pub suggestions: Vec<String>,
960 #[allow(missing_docs)]
961 pub notes: Vec<String>,
962 pub span_start: usize,
963 pub span_end: usize,
964}
965impl RichError {
966 #[allow(dead_code)]
967 #[allow(missing_docs)]
968 pub fn error(msg: impl Into<String>, start: usize, end: usize) -> Self {
969 Self {
970 message: msg.into(),
971 severity: ErrorSeverityLevel::Error,
972 code: None,
973 suggestions: Vec::new(),
974 notes: Vec::new(),
975 span_start: start,
976 span_end: end,
977 }
978 }
979 #[allow(dead_code)]
980 #[allow(missing_docs)]
981 pub fn warning(msg: impl Into<String>, start: usize, end: usize) -> Self {
982 Self {
983 severity: ErrorSeverityLevel::Warning,
984 ..Self::error(msg, start, end)
985 }
986 }
987 #[allow(dead_code)]
988 #[allow(missing_docs)]
989 pub fn with_code(mut self, code: impl Into<String>) -> Self {
990 self.code = Some(code.into());
991 self
992 }
993 #[allow(dead_code)]
994 #[allow(missing_docs)]
995 pub fn with_suggestion(mut self, s: impl Into<String>) -> Self {
996 self.suggestions.push(s.into());
997 self
998 }
999 #[allow(dead_code)]
1000 #[allow(missing_docs)]
1001 pub fn with_note(mut self, n: impl Into<String>) -> Self {
1002 self.notes.push(n.into());
1003 self
1004 }
1005 #[allow(dead_code)]
1006 #[allow(missing_docs)]
1007 pub fn is_fatal(&self) -> bool {
1008 self.severity >= ErrorSeverityLevel::Fatal
1009 }
1010 #[allow(dead_code)]
1011 #[allow(missing_docs)]
1012 pub fn span_len(&self) -> usize {
1013 self.span_end.saturating_sub(self.span_start)
1014 }
1015 #[allow(dead_code)]
1016 #[allow(missing_docs)]
1017 pub fn format(&self) -> String {
1018 let mut out = format!("[{}] {}", self.severity, self.message);
1019 if let Some(code) = &self.code {
1020 out = format!("[{}][{}] {}", code, self.severity, self.message);
1021 }
1022 for sug in &self.suggestions {
1023 out.push_str(&format!("\n suggestion: {}", sug));
1024 }
1025 for note in &self.notes {
1026 out.push_str(&format!("\n note: {}", note));
1027 }
1028 out
1029 }
1030}
1031#[allow(missing_docs)]
1033pub struct ParseErrorFormatter<'a> {
1034 pub src: &'a str,
1036 pub filename: &'a str,
1038 #[allow(missing_docs)]
1040 pub context_lines: usize,
1041}
1042impl<'a> ParseErrorFormatter<'a> {
1043 #[allow(missing_docs)]
1045 pub fn new(src: &'a str, filename: &'a str) -> Self {
1046 Self {
1047 src,
1048 filename,
1049 context_lines: 2,
1050 }
1051 }
1052 #[allow(missing_docs)]
1054 pub fn with_context(mut self, n: usize) -> Self {
1055 self.context_lines = n;
1056 self
1057 }
1058 #[allow(missing_docs)]
1060 pub fn format(&self, err: &ParseError) -> String {
1061 let line = err.span.line;
1062 let col = err.span.column;
1063 let lines: Vec<&str> = self.src.lines().collect();
1064 let mut out = format!(
1065 "{}:{}:{}: error: {:?}\n",
1066 self.filename, line, col, err.kind
1067 );
1068 if line > 0 && line <= lines.len() {
1069 let start = line.saturating_sub(1 + self.context_lines);
1070 let end = (line + self.context_lines).min(lines.len());
1071 for (i, ln) in lines[start..end].iter().enumerate() {
1072 let lineno = start + i + 1;
1073 out.push_str(&format!("{:>4} | {}\n", lineno, ln));
1074 if lineno == line {
1075 let spaces = " ".repeat(col.saturating_sub(1));
1076 out.push_str(&format!(" | {}^\n", spaces));
1077 }
1078 }
1079 }
1080 out
1081 }
1082 #[allow(missing_docs)]
1084 pub fn format_all(&self, collector: &ParseErrorCollector) -> String {
1085 collector
1086 .errors()
1087 .iter()
1088 .map(|e| self.format(e))
1089 .collect::<Vec<_>>()
1090 .join("\n")
1091 }
1092}
1093#[allow(dead_code)]
1095#[allow(missing_docs)]
1096#[derive(Clone, Debug, Default)]
1097pub struct ParseErrorReport {
1098 pub filename: String,
1100 pub diagnostics: Vec<ParseDiagnostic>,
1102}
1103impl ParseErrorReport {
1104 #[allow(dead_code)]
1106 #[allow(missing_docs)]
1107 pub fn new(filename: &str) -> Self {
1108 Self {
1109 filename: filename.to_string(),
1110 diagnostics: Vec::new(),
1111 }
1112 }
1113 #[allow(dead_code)]
1115 #[allow(missing_docs)]
1116 pub fn add(&mut self, d: ParseDiagnostic) {
1117 self.diagnostics.push(d);
1118 }
1119 #[allow(dead_code)]
1121 #[allow(missing_docs)]
1122 pub fn error_count(&self) -> usize {
1123 self.diagnostics.iter().filter(|d| d.is_error()).count()
1124 }
1125 #[allow(dead_code)]
1127 #[allow(missing_docs)]
1128 pub fn warning_count(&self) -> usize {
1129 self.diagnostics
1130 .iter()
1131 .filter(|d| d.severity == ErrorSeverity::Warning)
1132 .count()
1133 }
1134 #[allow(dead_code)]
1136 #[allow(missing_docs)]
1137 pub fn is_clean(&self) -> bool {
1138 self.error_count() == 0
1139 }
1140 #[allow(dead_code)]
1142 #[allow(missing_docs)]
1143 pub fn errors(&self) -> Vec<&ParseDiagnostic> {
1144 errors_only(&self.diagnostics)
1145 }
1146 #[allow(dead_code)]
1148 #[allow(missing_docs)]
1149 pub fn warnings(&self) -> Vec<&ParseDiagnostic> {
1150 warnings_only(&self.diagnostics)
1151 }
1152}
1153#[allow(dead_code)]
1155#[allow(missing_docs)]
1156pub struct ErrorChain {
1157 chain: Vec<RichError>,
1158}
1159impl ErrorChain {
1160 #[allow(dead_code)]
1161 #[allow(missing_docs)]
1162 pub fn new(root: RichError) -> Self {
1163 Self { chain: vec![root] }
1164 }
1165 #[allow(dead_code)]
1166 #[allow(missing_docs)]
1167 pub fn caused_by(mut self, e: RichError) -> Self {
1168 self.chain.push(e);
1169 self
1170 }
1171 #[allow(dead_code)]
1172 #[allow(missing_docs)]
1173 pub fn root(&self) -> &RichError {
1174 &self.chain[0]
1175 }
1176 #[allow(dead_code)]
1177 #[allow(missing_docs)]
1178 pub fn len(&self) -> usize {
1179 self.chain.len()
1180 }
1181 #[allow(dead_code)]
1182 #[allow(missing_docs)]
1183 pub fn is_empty(&self) -> bool {
1184 self.chain.is_empty()
1185 }
1186 #[allow(dead_code)]
1187 #[allow(missing_docs)]
1188 pub fn format_chain(&self) -> String {
1189 self.chain
1190 .iter()
1191 .enumerate()
1192 .map(|(i, e)| {
1193 if i == 0 {
1194 e.format()
1195 } else {
1196 format!(" caused by: {}", e.message)
1197 }
1198 })
1199 .collect::<Vec<_>>()
1200 .join("\n")
1201 }
1202}
1203#[derive(Clone, Debug)]
1205#[allow(missing_docs)]
1206pub struct ParseErrorBudget {
1207 pub budget: usize,
1209 pub remaining: usize,
1211}
1212impl ParseErrorBudget {
1213 #[allow(missing_docs)]
1215 pub fn new(budget: usize) -> Self {
1216 Self {
1217 budget,
1218 remaining: budget,
1219 }
1220 }
1221 #[allow(missing_docs)]
1223 pub fn consume(&mut self) -> bool {
1224 if self.remaining > 0 {
1225 self.remaining -= 1;
1226 true
1227 } else {
1228 false
1229 }
1230 }
1231 #[allow(missing_docs)]
1233 pub fn is_exhausted(&self) -> bool {
1234 self.remaining == 0
1235 }
1236 #[allow(missing_docs)]
1238 pub fn consumed(&self) -> usize {
1239 self.budget - self.remaining
1240 }
1241 #[allow(missing_docs)]
1243 pub fn reset(&mut self) {
1244 self.remaining = self.budget;
1245 }
1246}
1247#[allow(dead_code)]
1249#[allow(missing_docs)]
1250pub struct ErrorCodeCatalogue {
1251 entries: std::collections::HashMap<String, String>,
1252}
1253impl ErrorCodeCatalogue {
1254 #[allow(dead_code)]
1255 #[allow(missing_docs)]
1256 pub fn new() -> Self {
1257 let mut cat = Self {
1258 entries: std::collections::HashMap::new(),
1259 };
1260 cat.add("E0001", "unexpected token");
1261 cat.add("E0002", "missing closing bracket");
1262 cat.add("E0003", "expected identifier");
1263 cat.add("E0004", "invalid escape sequence");
1264 cat.add("E0005", "unterminated string literal");
1265 cat.add("E0006", "unexpected end of file");
1266 cat.add("E0007", "integer literal too large");
1267 cat.add("E0008", "invalid character");
1268 cat.add("E0009", "indentation error");
1269 cat.add("E0010", "ambiguous parse");
1270 cat
1271 }
1272 #[allow(dead_code)]
1273 #[allow(missing_docs)]
1274 pub fn add(&mut self, code: impl Into<String>, desc: impl Into<String>) {
1275 self.entries.insert(code.into(), desc.into());
1276 }
1277 #[allow(dead_code)]
1278 #[allow(missing_docs)]
1279 pub fn description(&self, code: &str) -> Option<&str> {
1280 self.entries.get(code).map(|s| s.as_str())
1281 }
1282 #[allow(dead_code)]
1283 #[allow(missing_docs)]
1284 pub fn count(&self) -> usize {
1285 self.entries.len()
1286 }
1287}
1288#[allow(dead_code)]
1290#[allow(missing_docs)]
1291pub struct QuickFixRegistry {
1292 fixes: std::collections::HashMap<String, Vec<String>>,
1293}
1294impl QuickFixRegistry {
1295 #[allow(dead_code)]
1296 #[allow(missing_docs)]
1297 pub fn new() -> Self {
1298 Self {
1299 fixes: std::collections::HashMap::new(),
1300 }
1301 }
1302 #[allow(dead_code)]
1303 #[allow(missing_docs)]
1304 pub fn register(&mut self, code: impl Into<String>, fix: impl Into<String>) {
1305 self.fixes.entry(code.into()).or_default().push(fix.into());
1306 }
1307 #[allow(dead_code)]
1308 #[allow(missing_docs)]
1309 pub fn fixes_for(&self, code: &str) -> &[String] {
1310 self.fixes.get(code).map(|v| v.as_slice()).unwrap_or(&[])
1311 }
1312 #[allow(dead_code)]
1313 #[allow(missing_docs)]
1314 pub fn has_fixes(&self, code: &str) -> bool {
1315 !self.fixes_for(code).is_empty()
1316 }
1317 #[allow(dead_code)]
1318 #[allow(missing_docs)]
1319 pub fn total_codes(&self) -> usize {
1320 self.fixes.len()
1321 }
1322}
1323#[allow(dead_code)]
1325#[allow(missing_docs)]
1326pub struct ErrorDeduplicator {
1327 seen: std::collections::HashSet<String>,
1328 suppressed: usize,
1329}
1330impl ErrorDeduplicator {
1331 #[allow(dead_code)]
1332 #[allow(missing_docs)]
1333 pub fn new() -> Self {
1334 Self {
1335 seen: std::collections::HashSet::new(),
1336 suppressed: 0,
1337 }
1338 }
1339 #[allow(dead_code)]
1340 #[allow(missing_docs)]
1341 pub fn should_emit(&mut self, key: &str) -> bool {
1342 if self.seen.contains(key) {
1343 self.suppressed += 1;
1344 false
1345 } else {
1346 self.seen.insert(key.to_string());
1347 true
1348 }
1349 }
1350 #[allow(dead_code)]
1351 #[allow(missing_docs)]
1352 pub fn suppressed_count(&self) -> usize {
1353 self.suppressed
1354 }
1355 #[allow(dead_code)]
1356 #[allow(missing_docs)]
1357 pub fn unique_count(&self) -> usize {
1358 self.seen.len()
1359 }
1360}
1361#[allow(dead_code)]
1363#[allow(missing_docs)]
1364pub struct ContextualRichError {
1365 pub error: RichError,
1366 pub context_snippet: String,
1367 pub file: String,
1368 pub line: usize,
1369 #[allow(missing_docs)]
1370 pub column: usize,
1371}
1372impl ContextualRichError {
1373 #[allow(dead_code)]
1374 #[allow(missing_docs)]
1375 pub fn new(error: RichError, source: &str, file: impl Into<String>) -> Self {
1376 let resolver = ErrorLocationResolver::new(source);
1377 let (line, col) = resolver.resolve(error.span_start);
1378 let snippet = resolver.snippet(error.span_start, 1);
1379 Self {
1380 error,
1381 context_snippet: snippet,
1382 file: file.into(),
1383 line,
1384 column: col,
1385 }
1386 }
1387 #[allow(dead_code)]
1388 #[allow(missing_docs)]
1389 pub fn format_full(&self) -> String {
1390 format!(
1391 "{}:{}:{}: {}\n{}",
1392 self.file,
1393 self.line + 1,
1394 self.column + 1,
1395 self.error.format(),
1396 self.context_snippet
1397 )
1398 }
1399}