1use crate::syntax::lexer::Span;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct LineCol {
6 line: usize,
7 col: usize,
8}
9
10impl fmt::Display for LineCol {
11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12 let LineCol { line, col } = self;
13 write!(f, "line {}, col {}", line + 1, col + 1)
15 }
16}
17
18fn get_line_col_vec(source: &str) -> Vec<LineCol> {
19 let mut vec = Vec::with_capacity(source.len());
20 let mut line = 0;
21 let mut col = 0;
22 for ch in source.chars() {
23 vec.push(LineCol { line, col });
24 if ch == '\n' {
25 line += 1;
26 col = 0;
27 } else {
28 col += 1;
29 }
30 }
31 vec
32}
33
34#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
35pub enum DiagLevel {
36 Error,
37 Warn,
38 Info,
39}
40
41impl fmt::Display for DiagLevel {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 match self {
44 DiagLevel::Error => write!(f, "Error"),
45 DiagLevel::Warn => write!(f, "Warn"),
46 DiagLevel::Info => write!(f, "Info"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct Description {
53 verbosity: u8,
54 message: String,
55 span: Option<Span>,
56}
57
58impl Description {
59 pub fn message<S: Into<String>>(msg: S) -> Description {
60 Description {
61 verbosity: 10,
62 message: msg.into(),
63 span: None,
64 }
65 }
66
67 pub fn with_span(mut self, span: Span) -> Description {
68 self.span = Some(span);
69 self
70 }
71
72 pub fn with_verbosity(mut self, verbosity: u8) -> Description {
73 self.verbosity = verbosity;
74 self
75 }
76}
77
78#[derive(Clone, Debug, PartialEq)]
79pub struct Diagnostic {
80 pub level: DiagLevel,
81 title: String,
82 descriptions: Vec<Description>,
83}
84
85impl Diagnostic {
86 pub fn error<S: Into<String>>(title: S) -> Diagnostic {
87 Diagnostic {
88 level: DiagLevel::Error,
89 title: title.into(),
90 descriptions: Vec::new(),
91 }
92 }
93
94 pub fn warn<S: Into<String>>(title: S) -> Diagnostic {
95 Diagnostic {
96 level: DiagLevel::Warn,
97 title: title.into(),
98 descriptions: Vec::new(),
99 }
100 }
101
102 pub fn info<S: Into<String>>(title: S) -> Diagnostic {
103 Diagnostic {
104 level: DiagLevel::Info,
105 title: title.into(),
106 descriptions: Vec::new(),
107 }
108 }
109
110 pub fn line<S: Into<String>>(mut self, msg: S) -> Diagnostic {
111 self.descriptions.push(Description::message(msg));
112 self
113 }
114
115 pub fn line_span<S: Into<String>>(mut self, span: Span, msg: S) -> Diagnostic {
116 self.descriptions
117 .push(Description::message(msg).with_span(span));
118 self
119 }
120
121 pub fn line_verb<S: Into<String>>(mut self, verbosity: u8, msg: S) -> Diagnostic {
122 self.descriptions
123 .push(Description::message(msg).with_verbosity(verbosity));
124 self
125 }
126
127 pub fn line_span_verb<S: Into<String>>(
128 mut self,
129 span: Span,
130 verbosity: u8,
131 msg: S,
132 ) -> Diagnostic {
133 self.descriptions.push(
134 Description::message(msg)
135 .with_span(span)
136 .with_verbosity(verbosity),
137 );
138 self
139 }
140
141 pub fn minimal_report(&self, source: &str, verbosity: u8) -> String {
143 let mut output = format!("[{}]: {}\n", self.level, &self.title);
144 let linecol = get_line_col_vec(source);
145 for descr in &self.descriptions {
146 if descr.verbosity > verbosity {
147 continue;
149 }
150 match &descr.span {
151 Some(span) => {
152 output.push_str(&format!(
153 "{} - {}:\n{}\n",
154 linecol[span.start], linecol[span.end], descr.message,
155 ));
156 }
157 None => {
158 output.push_str(&descr.message);
159 output.push('\n');
160 }
161 }
162 }
163 output
164 }
165
166 pub fn report(&self, source: &str, verbosity: u8) -> String {
167 let mut output = format!("[{}]: {}\n", self.level, &self.title);
168 let linecol = get_line_col_vec(source);
169 for descr in &self.descriptions {
170 if descr.verbosity > verbosity {
171 continue;
173 }
174 match &descr.span {
175 Some(span) => {
176 let start_line = linecol[span.start].line;
177 let start_col = linecol[span.start].col;
178 let end_line = linecol[span.end].line;
179 let end_col = linecol[span.end].col;
180 let text = source.lines().collect::<Vec<&str>>();
181 let mut vec: Vec<(usize, usize)> = Vec::new();
182 if start_line == end_line {
183 vec.push((start_col, end_col))
184 } else {
185 for (idx, line) in
186 text.iter().enumerate().take(end_line + 1).skip(start_line)
187 {
188 if idx == start_line {
189 vec.push((start_col, line.chars().count()))
190 } else if idx == end_line {
191 vec.push((0, end_col))
192 } else {
193 vec.push((0, line.chars().count()))
194 }
195 }
196 }
197
198 let head_width = (1 + end_line).to_string().len();
199 for (line, (s, e)) in (start_line..=end_line).zip(vec.into_iter()) {
200 output.push_str(&format!("{:head_width$} | {}\n", line + 1, text[line]));
202 output.push_str(&format!("{:head_width$} | ", ' '));
203 for _ in 0..s {
204 output.push(' ');
205 }
206 if line == start_line {
207 output.push('^');
208 for _ in s + 1..e {
209 output.push('~');
210 }
211 } else if line == end_line {
212 for _ in s..e {
213 output.push('~');
214 }
215 output.push('^');
216 } else {
217 for _ in s..e {
218 output.push('~');
219 }
220 }
221 output.push('\n');
222 }
223 output.push_str(&descr.message);
224 output.push('\n');
225 }
226 None => {
227 output.push_str(&descr.message);
228 output.push('\n');
229 }
230 }
231 }
232 output
233 }
234}
235
236#[test]
237fn diagnostic_test() {
238 let source = r#"1234567890
2391234567890
2401234567890
241"#;
242
243 let span = Span { start: 6, end: 25 };
244 let diag = Diagnostic::error("Error Name")
245 .line("some error description")
246 .line_span(span, "some spanned error description")
247 .line_verb(100, "this should not appear!");
248
249 assert_eq!(
250 diag.minimal_report(source, 30),
251 r#"[Error]: Error Name
252some error description
253line 1, col 7 - line 3, col 4:
254some spanned error description
255"#
256 );
257 assert_eq!(
258 diag.report(source, 30),
259 r#"[Error]: Error Name
260some error description
2611 | 1234567890
262 | ^~~~
2632 | 1234567890
264 | ~~~~~~~~~~
2653 | 1234567890
266 | ~~~^
267some spanned error description
268"#
269 );
270}