bork/
lib.rs

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// #[test]
467// fn xxopasdasd() {
468//     let mut source = Source::new("src/lib.rs", include_str!("../../../pose/src/lib.rs"));
469
470//     let errors = Error::TaggedRanges(
471//         "Oh no!",
472//         vec![
473//             Error::Range(317..331),
474//             Error::TaggedRanges("This is broken.", vec![Error::Range(324..330)]),
475//             //
476//         ],
477//     );
478
479//     source.add_error(errors);
480//     source.add_message(318..321, "All of my problems start here.");
481
482//     println!("{}", source);
483// }