1use std::collections::HashMap;
2use std::ops::RangeBounds;
3use std::ops::Bound;
4use std::ops::Range;
5use std::rc::Rc;
6use std::fmt::Display;
7use std::path::{Path, PathBuf};
8
9pub trait SourceErrorBounds {
10 fn bounds(&self) -> Range<usize>;
11}
12
13#[derive(Clone, Debug, PartialEq)]
14pub enum SourceError {
15 Range(Range<usize>),
16 TaggedRange(String, Range<usize>),
17 Ranges(Vec<SourceError>),
18 TaggedRanges(String, Vec<SourceError>),
19}
20
21impl SourceError {
22 #[inline]
23 pub fn tag<S>(self, tag: S) -> Self
24 where
25 S: AsRef<str>,
26 {
27 match self {
28 SourceError::Range(range) => SourceError::TaggedRange(tag.as_ref().into(), range),
29 _ => SourceError::TaggedRanges(tag.as_ref().into(), vec![self]),
30 }
31 }
32}
33
34impl SourceErrorBounds for SourceError {
35 fn bounds(&self) -> Range<usize> {
36 match self {
37 SourceError::Range(bounds) | SourceError::TaggedRange(_, bounds) => bounds.clone(),
38 SourceError::Ranges(errors) | SourceError::TaggedRanges(_, errors) => errors.bounds(),
39 }
40 }
41}
42
43impl SourceErrorBounds for Vec<SourceError> {
44 fn bounds(&self) -> Range<usize> {
45 let mut start = std::usize::MAX;
46 let mut end = 0;
47
48 for error in self {
49 let bounds = error.bounds();
50
51 if start > bounds.start {
52 start = bounds.start;
53 }
54
55 if end < bounds.end {
56 end = bounds.end;
57 }
58 }
59
60 start..end
61 }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub struct MessagePosition(usize, usize);
66
67pub type RcMessage = Rc<Message>;
68
69#[derive(Clone, Debug, PartialEq)]
70pub struct Message {
71 bounds: Range<usize>,
72 message: String,
73}
74
75#[derive(Clone, Debug, PartialEq)]
76pub enum SourcePosition {
77 LineCol(usize, usize),
78 Unknown,
79}
80
81impl Display for SourcePosition {
82 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
83 match self {
84 SourcePosition::LineCol(line, col) => write!(f, "{}:{}", line, col),
85 SourcePosition::Unknown => write!(f, "unknown"),
86 }
87 }
88}
89
90#[derive(Clone, Debug, PartialEq)]
91pub struct LineMessage {
92 message: RcMessage,
93 bounds: Range<usize>,
94}
95
96#[derive(Clone, Debug, PartialEq)]
97pub struct Line<'a> {
98 line_no: usize,
99 bounds: Range<usize>,
100 messages: Vec<LineMessage>,
101 source: &'a str,
102}
103
104impl<'a> Line<'a> {
105 pub fn add_message(&mut self, message: RcMessage) {
106 if self.bounds.contains(&message.bounds.start) {
107 if self.bounds.contains(&message.bounds.end) {
108 self.messages.push(LineMessage {
109 bounds: message.bounds.start - self.bounds.start
110 ..message.bounds.end - self.bounds.start,
111 message,
112 });
113 } else {
114 self.messages.push(LineMessage {
115 bounds: message.bounds.start - self.bounds.start..self.source.len(),
116 message,
117 });
118 }
119 } else if self.bounds.contains(&message.bounds.end) {
120 self.messages.push(LineMessage {
121 bounds: 0..message.bounds.end - self.bounds.start,
122 message,
123 });
124 } else if message.bounds.start < self.bounds.start && message.bounds.end > self.bounds.end {
125 self.messages.push(LineMessage {
126 bounds: 0..self.source.len(),
127 message,
128 });
129 }
130 }
131
132 pub fn has_messages(&self) -> bool {
133 !self.messages.is_empty()
134 }
135}
136
137#[derive(Clone, Debug, PartialEq)]
138pub struct Source<'a> {
139 pub filename: PathBuf,
140 pub lines: Vec<Line<'a>>,
141 pub messages: Vec<RcMessage>,
142}
143
144impl<'a> Source<'a> {
145 pub fn new<P>(filename: P, source: &'a str) -> Self
146 where
147 P: AsRef<Path> + 'a,
148 {
149 let filename = filename.as_ref().to_path_buf();
150 let mut lines = Vec::new();
151 let messages = Vec::new();
152
153 for (mut cur_line_no, cur_line) in source.lines().enumerate() {
154 let cur_offset = (cur_line.as_ptr() as usize) - (source.as_ptr() as usize);
155
156 cur_line_no += 1;
157
158 lines.push(Line {
159 line_no: cur_line_no,
160 bounds: cur_offset..cur_offset + cur_line.len(),
161 messages: Vec::new(),
162 source: cur_line,
163 });
164 }
165
166 Source {
167 filename,
168 lines,
169 messages,
170 }
171 }
172
173 pub fn add_error(&mut self, error: SourceError) {
174 match error {
175 SourceError::Range(_) => {}
176 SourceError::TaggedRange(message, bounds) => {
177 self.add_message(bounds, message);
178 }
179 SourceError::Ranges(errors) => for error in errors {
180 self.add_error(error);
181 },
182 SourceError::TaggedRanges(message, errors) => {
183 self.add_message(errors.bounds(), message);
184
185 for error in errors {
186 self.add_error(error);
187 }
188 }
189 }
190 }
191
192 pub fn add_message<B, S>(&mut self, bounds: B, message: S)
193 where
194 B: RangeBounds<usize>,
195 S: AsRef<str>,
196 {
197 let start_bound: usize = match bounds.start_bound() {
198 Bound::Excluded(val) => *val,
199 Bound::Included(val) => *val,
200 Bound::Unbounded => 0,
201 };
202 let end_bound: usize = match bounds.end_bound() {
203 Bound::Excluded(val) => *val,
204 Bound::Included(val) => *val + 1,
205 Bound::Unbounded => 0,
206 };
207 let bounds = start_bound..end_bound;
208
209 let message = message.as_ref().to_string();
210 let message = Rc::new(Message { bounds, message });
211
212 self.messages.push(message.clone());
213
214 for line in &mut self.lines {
215 if line.bounds.contains(&message.bounds.start)
216 || line.bounds.contains(&message.bounds.end)
217 || (message.bounds.start < line.bounds.start
218 && message.bounds.end > line.bounds.end)
219 {
220 line.add_message(message.clone());
221 }
222 }
223 }
224
225 pub fn position<B>(&self, bounds: B) -> SourcePosition
226 where
227 B: RangeBounds<usize>,
228 {
229 let start_bound: usize = match bounds.start_bound() {
230 Bound::Excluded(val) => *val,
231 Bound::Included(val) => *val,
232 Bound::Unbounded => 0,
233 };
234
235 for line in &self.lines {
236 if line.bounds.contains(&start_bound) {
237 return SourcePosition::LineCol(line.line_no, 1 + (start_bound - line.bounds.start));
238 }
239 }
240
241 SourcePosition::Unknown
242 }
243}
244
245impl<'a> Display for Source<'a> {
246 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
247 let line_nos = self.lines.len();
248 let line_no_width = 2 + format!("{}", self.lines.len()).len();
249
250 let mut groups = Vec::new();
251 let mut group = Vec::new();
252
253 for line in &self.lines {
254 if line.has_messages() {
255 group.push(line);
256 } else if !group.is_empty() {
257 groups.push(group);
258 group = Vec::new();
259 }
260 }
261
262 let mut write_line = |line_no: usize, line: &str| {
263 writeln!(
264 f,
265 "{line_no:>line_no_width$} │ {line}",
266 line = line,
267 line_no = if line_no > 0 {
268 format!("{}", line_no)
269 } else {
270 "".into()
271 },
272 line_no_width = line_no_width,
273 )
274 };
275
276 for group in groups {
277 let mut message_id = 1;
278 let messages = group
279 .clone()
280 .into_iter()
281 .fold(Vec::new(), |mut messages, line| {
282 for message in &line.messages {
283 if !messages.contains(&message.message) {
284 messages.push(message.message.clone());
285 }
286 }
287
288 messages
289 });
290
291 println!("");
292
293 for message in &messages {
294 let cur_id = format!("#{} - ", message_id);
295
296 println!(
297 "{}{}\n{}@ {}:{}\n",
298 cur_id,
299 message.message,
300 " ".repeat(cur_id.len()),
301 self.filename.display(),
302 self.position(message.bounds.clone())
303 );
304
305 message_id += 1;
306 }
307
308 if let Some(first) = group.first() {
309 let first_line_no = if first.line_no > 4 {
310 first.line_no - 4
311 } else {
312 1
313 };
314 let last_line_no = if first.line_no > 2 {
315 first.line_no - 1
316 } else {
317 1
318 };
319
320 if first_line_no > 1 {
321 write_line(0, "···")?;
322 }
323
324 if let Some(lines) = self.lines.get(first_line_no..last_line_no) {
325 for line in lines {
326 write_line(line.line_no, line.source)?;
327 }
328 }
329 }
330
331 for line in &group {
332 write_line(line.line_no, line.source)?;
333
334 for (index, message) in line.messages.clone().into_iter().enumerate() {
335 let mut intersections = Vec::new();
336 let mut end = message.bounds.end;
337
338 for following in line.messages.get(index + 1..).unwrap() {
339 intersections.push(following.bounds.start);
340 intersections.push(following.bounds.end);
341
342 if end < following.bounds.end {
343 end = following.bounds.end;
344 }
345 }
346
347 for preceding in line.messages.get(..index).unwrap() {
348 intersections.push(preceding.bounds.start);
349
350 if end < preceding.bounds.end {
351 end = preceding.bounds.end;
352 }
353 }
354
355 if message.bounds.start == message.bounds.end && !intersections.is_empty() {
356 continue;
357 }
358
359 let mut diagram = String::new();
360
361 for position in 0..=end {
362 let is_intersected = intersections.contains(&position);
363 let is_start_bound = position == message.bounds.start;
364 let is_end_bound = position == message.bounds.end;
365 let is_in_bounds =
366 position < message.bounds.end && position > message.bounds.start;
367
368 if is_end_bound && is_start_bound {
369 diagram.push('│');
370 } else if is_end_bound {
371 if is_intersected {
372 diagram.push('┤');
373 } else {
374 diagram.push('┘');
375 }
376 } else if is_start_bound {
377 diagram.push('├');
378 } else if is_intersected {
379 diagram.push('│');
380 } else if is_in_bounds {
381 diagram.push('─');
382 } else {
383 diagram.push(' ');
384 }
385 }
386
387 write_line(0, &diagram)?;
388 }
389
390 let mut message_map: HashMap<usize, Vec<String>> = HashMap::new();
391 let mut depth = 0;
392 let mut end = 0;
393
394 for line_message in &line.messages {
395 let entry = message_map
396 .entry(line_message.bounds.start)
397 .or_insert(Vec::new());
398
399 entry.push(format!(
400 "{}",
401 1 + messages
402 .clone()
403 .iter()
404 .position(|r| r == &line_message.message)
405 .unwrap()
406 ));
407
408 if end < line_message.bounds.start {
409 end = line_message.bounds.start;
410 }
411
412 if depth < entry.len() {
413 depth = entry.len()
414 }
415 }
416
417 for depth in 0..depth {
418 let mut key = String::new();
419
420 for col in 0..=end {
421 if let Some(entry) = message_map.get(&col) {
422 if let Some(id) = entry.get(depth) {
423 key.push_str(&format!("{}", id));
424 } else {
425 key.push(' ');
426 }
427 } else {
428 key.push(' ');
429 }
430 }
431
432 write_line(0, &key)?;
433 }
434
435 write_line(0, "")?;
436 }
437
438 if let Some(last) = group.last() {
439 let last_line_no = if last.line_no < line_nos - 4 {
440 last.line_no + 4
441 } else {
442 line_nos
443 };
444 let first_line_no = if last.line_no < line_nos - 2 {
445 last.line_no
446 } else {
447 line_nos
448 };
449
450 if let Some(lines) = self.lines.get(first_line_no..last_line_no) {
451 for line in lines {
452 write_line(line.line_no, line.source)?;
453 }
454 }
455
456 if last.line_no < line_nos {
457 write_line(0, "···")?;
458 }
459 }
460 }
461
462 Ok(())
463 }
464}
465
466