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