1use crate::ast::parse::Rule;
2
3pub fn print_error(filename: &str, source: &str, error: pest::error::Error<Rule>) {
4 use codespan_reporting::diagnostic::{Diagnostic, Label};
5 use codespan_reporting::files::SimpleFile;
6 use codespan_reporting::term::{self, Config};
7 use pest::error::LineColLocation;
8
9 let file = SimpleFile::new(filename, source);
10
11 let (start_offset, end_offset) = match &error.line_col {
13 LineColLocation::Pos((line, col)) => {
14 let line_offsets: Vec<usize> = source
16 .char_indices()
17 .filter_map(|(i, c)| if c == '\n' { Some(i) } else { None })
18 .collect();
19
20 let line_idx = line - 1; let line_start = if line_idx == 0 {
22 0
23 } else {
24 line_offsets[line_idx - 1] + 1
25 };
26 let offset = line_start + col - 1;
27
28 (offset, offset + 1) }
30 LineColLocation::Span((start_line, start_col), (end_line, end_col)) => {
31 let line_offsets: Vec<usize> = source
33 .char_indices()
34 .filter_map(|(i, c)| if c == '\n' { Some(i) } else { None })
35 .collect();
36
37 let start_line_idx = start_line - 1; let start_line_offset = if start_line_idx == 0 {
40 0
41 } else {
42 line_offsets[start_line_idx - 1] + 1
43 };
44 let start_pos = start_line_offset + start_col - 1;
45
46 let end_line_idx = end_line - 1; let end_line_offset = if end_line_idx == 0 {
49 0
50 } else {
51 line_offsets[end_line_idx - 1] + 1
52 };
53 let end_pos = end_line_offset + end_col;
54
55 (start_pos, end_pos)
56 }
57 };
58
59 let error_text = if end_offset > start_offset && end_offset <= source.len() {
61 source[start_offset..end_offset].to_string()
62 } else {
63 "".to_string()
64 };
65
66 let message = match &error.variant {
68 pest::error::ErrorVariant::ParsingError {
69 positives,
70 negatives,
71 } => {
72 if !positives.is_empty() {
73 format!("Expected {}", format_rules(positives))
74 } else if !negatives.is_empty() {
75 format!("Unexpected {}", format_rules(negatives))
76 } else {
77 "Parsing error".to_string()
78 }
79 }
80 pest::error::ErrorVariant::CustomError { message } => message.clone(),
81 };
82
83 let mut notes = Vec::new();
85
86 match &error.variant {
87 pest::error::ErrorVariant::ParsingError {
88 positives,
89 negatives,
90 } => {
91 if !positives.is_empty() && !negatives.is_empty() {
92 notes.push(format!(
93 "Found `{}`, but expected {}",
94 if error_text.is_empty() {
95 "???"
96 } else {
97 &error_text
98 },
99 format_rules(positives)
100 ));
101 }
102 }
103 _ => {}
104 }
105
106 let mut diagnostic = Diagnostic::error()
108 .with_message("Syntax error")
109 .with_labels(vec![
110 Label::primary((), start_offset..end_offset).with_message(message),
111 ]);
112
113 if !notes.is_empty() {
115 diagnostic = diagnostic.with_notes(notes);
116 }
117
118 let writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Auto);
120 let config = Config::default();
121 term::emit(&mut writer.lock(), &config, &file, &diagnostic).unwrap();
122}
123
124fn format_rules(rules: &[Rule]) -> String {
126 if rules.is_empty() {
127 return "nothing".to_string();
128 }
129
130 let rule_strings: Vec<String> = rules.iter().map(|rule| format!("`{:?}`", rule)).collect();
131
132 if rule_strings.len() == 1 {
133 rule_strings[0].clone()
134 } else {
135 let last = rule_strings.last().unwrap();
136 let rest = &rule_strings[..rule_strings.len() - 1];
137 format!("{} or {}", rest.join(", "), last)
138 }
139}