1use crate::internal::*;
2
3#[derive(Debug, Eq, PartialEq, Clone)]
4pub struct Diagnostic {
5 pub line_num: u32,
6 pub line: String,
7 pub message: String,
8 pub underline_start: u32,
9 pub underline_width: u32,
10 pub source_file: SourceFile,
11}
12
13impl Parser<'_> {
14 pub(crate) fn err_line(&self, message: impl Into<String>, line: &Line) -> Result<()> {
15 let loc = line.first_loc().expect("non empty line for `err_line`");
16 let (line_num, offset) = self.lexer.line_number_with_offset(loc);
17 let line = line.reassemble_src().to_string();
18 self.handle_err(Diagnostic {
19 line_num,
20 message: message.into(),
21 underline_start: offset,
22 underline_width: line.len() as u32,
23 line,
24 source_file: self.lexer.source_file_at(loc.include_depth).clone(),
25 })
26 }
27
28 pub(crate) fn err_line_starting(
29 &self,
30 message: impl Into<String>,
31 loc: SourceLocation,
32 ) -> Result<()> {
33 let (line_num, offset) = self.lexer.line_number_with_offset(loc);
34 let line = self.lexer.line_of(loc);
35 self.handle_err(Diagnostic {
36 line_num,
37 message: message.into(),
38 underline_start: offset,
39 underline_width: line.len() as u32,
40 line: String::from(line.as_str()),
41 source_file: self.lexer.source_file_at(loc.include_depth).clone(),
42 })
43 }
44
45 pub(crate) fn err_doc_attr(
46 &self,
47 key: impl Into<String>,
48 message: impl Into<String>,
49 ) -> Result<()> {
50 let key = &key.into();
51 for (idx, line) in self
52 .lexer
53 .raw_lines()
54 .enumerate()
55 .skip_while(|(_, l)| l.is_empty())
56 {
57 if line.is_empty() {
58 break; }
60 if line.starts_with(key) {
61 return self.handle_err(Diagnostic {
62 line_num: idx as u32 + 1,
63 line: line.to_string(),
64 message: message.into(),
65 underline_start: 0,
66 underline_width: line.len() as u32,
67 source_file: self.lexer.source_file().clone(),
68 });
69 }
70 }
71 debug_assert!(false, "doc attr not found");
72 Ok(())
73 }
74
75 pub(crate) fn err_at_pattern(
76 &self,
77 message: impl Into<String>,
78 line_start: SourceLocation,
79 pattern: &str,
80 ) -> Result<()> {
81 let (line_num, _) = self.lexer.line_number_with_offset(line_start);
82 let line = self.lexer.line_of(line_start);
83 if let Some(idx) = line.find(pattern) {
84 return self.handle_err(Diagnostic {
85 line_num,
86 line: line.to_string(),
87 message: message.into(),
88 underline_start: idx as u32,
89 underline_width: pattern.len() as u32,
90 source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
91 });
92 }
93 self.handle_err(Diagnostic {
94 line_num,
95 line: line.to_string(),
96 message: message.into(),
97 underline_start: 0,
98 underline_width: line.len() as u32,
99 source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
100 })
101 }
102
103 pub(crate) fn err_at(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
104 let (line_num, offset) = self.lexer.line_number_with_offset(loc);
105 self.handle_err(Diagnostic {
106 line_num,
107 line: self.lexer.line_of(loc).to_string(),
108 message: message.into(),
109 underline_start: offset,
110 underline_width: loc.end - loc.start,
111 source_file: self.lexer.source_file_at(loc.include_depth).clone(),
112 })
113 }
114
115 pub(crate) fn err_token_full(&self, message: impl Into<String>, token: &Token) -> Result<()> {
116 let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
117 self.handle_err(Diagnostic {
118 line_num,
119 line: self.lexer.line_of(token.loc).to_string(),
120 message: message.into(),
121 underline_start: offset,
122 underline_width: token.lexeme.len() as u32,
123 source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
124 })
125 }
126
127 pub(crate) fn err_token_start(&self, message: impl Into<String>, token: &Token) -> Result<()> {
128 let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
129 self.handle_err(Diagnostic {
130 line_num,
131 line: self.lexer.line_of(token.loc).to_string(),
132 message: message.into(),
133 underline_start: offset,
134 underline_width: 1,
135 source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
136 })
137 }
138
139 pub(crate) fn err_token(&self, message: impl Into<String>, token: Option<&Token>) -> Result<()> {
140 let location = token.map_or_else(|| self.lexer.loc(), |t| t.loc);
141 let (line_num, offset) = self.lexer.line_number_with_offset(location);
142 self.handle_err(Diagnostic {
143 line_num,
144 line: self.lexer.line_of(location).to_string(),
145 message: message.into(),
146 underline_start: offset,
147 underline_width: 1,
148 source_file: self.lexer.source_file_at(location.include_depth).clone(),
149 })
150 }
151
152 pub(crate) fn err(&self, diagnostic: Diagnostic) -> Result<()> {
153 self.handle_err(diagnostic)
154 }
155
156 fn handle_err(&self, err: Diagnostic) -> Result<()> {
157 if self.strict {
158 Err(err)
159 } else {
160 self.errors.borrow_mut().push(err);
161 Ok(())
162 }
163 }
164}
165
166pub trait DiagnosticColor {
167 fn line_num(&self, s: impl Into<String>) -> String {
168 s.into()
169 }
170 fn line(&self, s: impl Into<String>) -> String {
171 s.into()
172 }
173 fn location(&self, s: impl Into<String>) -> String {
174 s.into()
175 }
176 fn message(&self, s: impl Into<String>) -> String {
177 s.into()
178 }
179}
180
181impl Diagnostic {
182 pub fn plain_text(&self) -> String {
183 struct NoColor;
184 impl DiagnosticColor for NoColor {}
185 self.plain_text_with(NoColor)
186 }
187
188 pub fn plain_text_with<C: DiagnosticColor>(&self, colorizer: C) -> String {
189 let line_num_pad = match self.line_num {
190 n if n < 10 => 4,
191 n if n < 100 => 5,
192 n if n < 1000 => 6,
193 n if n < 10000 => 7,
194 _ => 8,
195 };
196 format!(
197 "{}{}{}{}{}{}{}\n{}{}\n{} {} {}\n{}{} {}{} {}\n",
198 " ".repeat((line_num_pad - 3) as usize),
199 colorizer.line_num("--> "),
200 colorizer.line_num(self.source_file.file_name()),
201 colorizer.line_num(":"),
202 colorizer.line_num(self.line_num.to_string()),
203 colorizer.line_num(":"),
204 colorizer.line_num((self.underline_start + 1).to_string()),
205 " ".repeat((line_num_pad - 2) as usize),
206 colorizer.line_num("|"),
207 colorizer.line_num(self.line_num.to_string()),
208 colorizer.line_num("|"),
209 colorizer.line(&self.line),
210 " ".repeat((line_num_pad - 2) as usize),
211 colorizer.line_num("|"),
212 " ".repeat(self.underline_start as usize),
213 colorizer.location("^".repeat(self.underline_width as usize)),
214 colorizer.message(&self.message),
215 )
216 }
217}