1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Span {
11 pub start: usize,
12 pub end: usize,
13}
14
15impl Span {
16 pub fn new(start: usize, end: usize) -> Self {
17 Span { start, end }
18 }
19
20 pub fn to(self, other: Span) -> Span {
22 Span::new(self.start, other.end)
23 }
24}
25
26#[derive(Debug, Clone)]
30pub struct LuxError {
31 pub message: String,
32 pub span: Span,
33 pub note: Option<String>,
34 pub learn: Option<(&'static str, &'static str)>,
35}
36
37impl LuxError {
38 pub fn new(message: impl Into<String>, span: Span) -> Self {
39 LuxError {
40 message: message.into(),
41 span,
42 note: None,
43 learn: None,
44 }
45 }
46
47 pub fn with_note(mut self, note: impl Into<String>) -> Self {
49 self.note = Some(note.into());
50 self
51 }
52
53 pub fn with_learn(mut self, topic: &'static str, lure: &'static str) -> Self {
58 self.learn = Some((topic, lure));
59 self
60 }
61}
62
63pub fn report(filename: &str, source: &str, err: &LuxError) {
65 let start = err.span.start.min(source.len());
66 let line_start = source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0);
67 let line_end = source[start..]
68 .find('\n')
69 .map(|i| start + i)
70 .unwrap_or(source.len());
71 let line_no = source[..line_start].bytes().filter(|&b| b == b'\n').count() + 1;
72 let col = start - line_start + 1;
73 let line_text = &source[line_start..line_end];
74
75 let caret_end = err.span.end.min(line_end);
76 let caret_len = caret_end.saturating_sub(start).max(1);
77
78 eprintln!("error: {}", err.message);
79 eprintln!(" --> {}:{}:{}", filename, line_no, col);
80
81 let gutter = format!(" {} | ", line_no);
82 let blank: String = gutter.chars().map(|_| ' ').collect();
83 eprintln!("{}{}", gutter, line_text);
84 eprintln!("{}{}{}", blank, " ".repeat(col - 1), "^".repeat(caret_len));
85
86 if let Some(note) = &err.note {
87 eprintln!("note: {}", note);
88 }
89 if let Some((topic, lure)) = &err.learn {
90 eprintln!("help: `lux learn {}` — {}", topic, lure);
91 }
92}