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_line_of(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
46 let (line_num, _) = self.lexer.line_number_with_offset(loc);
47 let line = self.lexer.line_of(loc);
48 self.handle_err(Diagnostic {
49 line_num,
50 message: message.into(),
51 underline_start: 0,
52 underline_width: line.len() as u32,
53 line: String::from(line.as_str()),
54 source_file: self.lexer.source_file_at(loc.include_depth).clone(),
55 })
56 }
57
58 pub(crate) fn err_doc_attr(
59 &self,
60 key: impl Into<String>,
61 message: impl Into<String>,
62 ) -> Result<()> {
63 let key: &String = &key.into();
64 let loc = self.ctx.attr_defs.iter().find_map(|def| {
65 if def.in_header && def.name == *key { Some(def.loc) } else { None }
66 });
67 let Some(loc) = loc else {
68 debug_assert!(false, "doc attr not found");
69 return Ok(());
70 };
71 self.err_line_of(message, loc)
72 }
73
74 pub(crate) fn err_at_pattern(
75 &self,
76 message: impl Into<String>,
77 line_start: SourceLocation,
78 pattern: &str,
79 ) -> Result<()> {
80 let (line_num, _) = self.lexer.line_number_with_offset(line_start);
81 let line = self.lexer.line_of(line_start);
82 if let Some(idx) = line.find(pattern) {
83 return self.handle_err(Diagnostic {
84 line_num,
85 line: line.to_string(),
86 message: message.into(),
87 underline_start: idx as u32,
88 underline_width: pattern.len() as u32,
89 source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
90 });
91 }
92 self.handle_err(Diagnostic {
93 line_num,
94 line: line.to_string(),
95 message: message.into(),
96 underline_start: 0,
97 underline_width: line.len() as u32,
98 source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
99 })
100 }
101
102 pub(crate) fn err_at(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
103 let (line_num, offset) = self.lexer.line_number_with_offset(loc);
104 self.handle_err(Diagnostic {
105 line_num,
106 line: self.lexer.line_of(loc).to_string(),
107 message: message.into(),
108 underline_start: offset,
109 underline_width: loc.end - loc.start,
110 source_file: self.lexer.source_file_at(loc.include_depth).clone(),
111 })
112 }
113
114 pub(crate) fn err_token_full(&self, message: impl Into<String>, token: &Token) -> Result<()> {
115 let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
116 self.handle_err(Diagnostic {
117 line_num,
118 line: self.lexer.line_of(token.loc).to_string(),
119 message: message.into(),
120 underline_start: offset,
121 underline_width: token.lexeme.len() as u32,
122 source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
123 })
124 }
125
126 pub(crate) fn err_token_start(&self, message: impl Into<String>, token: &Token) -> Result<()> {
127 let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
128 self.handle_err(Diagnostic {
129 line_num,
130 line: self.lexer.line_of(token.loc).to_string(),
131 message: message.into(),
132 underline_start: offset,
133 underline_width: 1,
134 source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
135 })
136 }
137
138 pub(crate) fn err_token(&self, message: impl Into<String>, token: Option<&Token>) -> Result<()> {
139 let location = token.map_or_else(|| self.lexer.loc(), |t| t.loc);
140 let (line_num, offset) = self.lexer.line_number_with_offset(location);
141 self.handle_err(Diagnostic {
142 line_num,
143 line: self.lexer.line_of(location).to_string(),
144 message: message.into(),
145 underline_start: offset,
146 underline_width: 1,
147 source_file: self.lexer.source_file_at(location.include_depth).clone(),
148 })
149 }
150
151 pub(crate) fn err(&self, diagnostic: Diagnostic) -> Result<()> {
152 self.handle_err(diagnostic)
153 }
154
155 fn handle_err(&self, err: Diagnostic) -> Result<()> {
156 if self.strict {
157 Err(err)
158 } else {
159 self.errors.borrow_mut().push(err);
160 Ok(())
161 }
162 }
163}
164
165pub trait DiagnosticColor {
166 fn line_num(&self, s: impl Into<String>) -> String {
167 s.into()
168 }
169 fn line(&self, s: impl Into<String>) -> String {
170 s.into()
171 }
172 fn location(&self, s: impl Into<String>) -> String {
173 s.into()
174 }
175 fn message(&self, s: impl Into<String>) -> String {
176 s.into()
177 }
178}
179
180impl Diagnostic {
181 pub fn plain_text(&self) -> String {
182 struct NoColor;
183 impl DiagnosticColor for NoColor {}
184 self.plain_text_with(NoColor)
185 }
186
187 pub fn plain_text_with<C: DiagnosticColor>(&self, colorizer: C) -> String {
188 let line_num_pad = match self.line_num {
189 n if n < 10 => 4,
190 n if n < 100 => 5,
191 n if n < 1000 => 6,
192 n if n < 10000 => 7,
193 _ => 8,
194 };
195 format!(
196 "{}{}{}{}{}{}{}\n{}{}\n{} {} {}\n{}{} {}{} {}\n",
197 " ".repeat((line_num_pad - 3) as usize),
198 colorizer.line_num("--> "),
199 colorizer.line_num(self.source_file.file_name()),
200 colorizer.line_num(":"),
201 colorizer.line_num(self.line_num.to_string()),
202 colorizer.line_num(":"),
203 colorizer.line_num((self.underline_start + 1).to_string()),
204 " ".repeat((line_num_pad - 2) as usize),
205 colorizer.line_num("|"),
206 colorizer.line_num(self.line_num.to_string()),
207 colorizer.line_num("|"),
208 colorizer.line(&self.line),
209 " ".repeat((line_num_pad - 2) as usize),
210 colorizer.line_num("|"),
211 " ".repeat(self.underline_start as usize),
212 colorizer.location("^".repeat(self.underline_width as usize)),
213 colorizer.message(&self.message),
214 )
215 }
216}