1use std::error::Error as ErrorTrait;
31use std::fmt::{self, Display};
32
33use colored::{Color, Colorize};
34use unicode_width::UnicodeWidthStr;
35
36use crate::cursor::InputCursor;
37use crate::lexer::Lexer;
38use crate::span::Span;
39use crate::symbol::SymbolGroup;
40use crate::token::{Token, TokenKind, TokenValue};
41
42struct LineIter<'a> {
43 s: &'a str,
44 eof: bool,
45}
46
47impl<'a> LineIter<'a> {
48 fn new(s: &'a str) -> Self {
49 Self { s, eof: false }
50 }
51}
52
53impl<'a> Iterator for LineIter<'a> {
54 type Item = &'a str;
55
56 fn next(&mut self) -> Option<Self::Item> {
57 if self.eof {
58 return None;
59 }
60
61 let mut iter = self.s.chars().peekable();
62 let mut line = "";
63 self.eof = true;
64
65 while let Some(c) = iter.next() {
66 match c {
67 '\r' if iter.peek() == Some(&'\n') => self.s = &self.s[(line.len() + 2)..],
68 '\n' if iter.peek() == Some(&'\r') => self.s = &self.s[(line.len() + 2)..],
69 '\r' | '\n' => self.s = &self.s[(line.len() + 1)..],
70 _ => line = &self.s[..(line.len() + c.len_utf8())],
71 }
72
73 if c == '\r' || c == '\n' {
74 self.eof = false;
75 return Some(line);
76 }
77 }
78
79 self.s = &self.s[line.len()..];
80
81 Some(line)
82 }
83}
84
85#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
87pub enum Severity {
88 Error,
89 Warning,
90}
91
92impl Display for Severity {
93 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
94 let s = match self {
95 Self::Error => "error",
96 Self::Warning => "warning",
97 };
98
99 if formatter.alternate() {
100 let s = match self {
101 Self::Error => s.bright_red(),
102 Self::Warning => s.yellow(),
103 }.bold();
104
105 write!(formatter, "{}", s)
106 } else {
107 write!(formatter, "{}", s)
108 }
109 }
110}
111
112impl Default for Severity {
113 fn default() -> Self {
114 Self::Error
115 }
116}
117
118#[derive(Clone, Debug, Eq, Hash, PartialEq)]
120pub struct Error {
121 span: Span,
122 severity: Severity,
123 message: String,
124}
125
126impl Display for Error {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 if !formatter.alternate() {
129 write!(formatter, "{}", self.message)
130 } else {
131 write!(
132 formatter,
133 "{:#}: {} ({})",
134 self.severity, self.message, self.span
135 )
136 }
137 }
138}
139
140impl ErrorTrait for Error {}
141
142impl Error {
143 pub fn new(span: Span, message: impl Display) -> Self {
145 Self {
146 span,
147 severity: Default::default(),
148 message: message.to_string(),
149 }
150 }
151
152 pub fn with_severity(self, severity: Severity) -> Self {
154 Self { severity, ..self }
155 }
156
157 pub fn with_buffer<'a>(&'a self, buf: &'a str) -> ErrorPrinter<'a> {
161 ErrorPrinter { error: &self, buf }
162 }
163
164 pub fn span(&self) -> Span {
166 self.span
167 }
168
169 pub fn severity(&self) -> Severity {
171 self.severity
172 }
173}
174
175#[derive(Debug)]
177pub struct ErrorPrinter<'a> {
178 error: &'a Error,
179 buf: &'a str,
180}
181
182impl Display for ErrorPrinter<'_> {
183 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
184 if formatter.alternate() {
185 writeln!(formatter, "{:#}", self.error)?;
186 display_source_for_span(formatter, self.error.span, self.buf)
187 } else {
188 write!(formatter, "{}", self.error)
189 }
190 }
191}
192
193fn get_color_for_token(token: &Token<'_>) -> Option<Color> {
194 match token.kind() {
195 TokenKind::Number => Some(Color::BrightCyan),
196 TokenKind::String => Some(Color::Yellow),
197 TokenKind::Comment => Some(Color::BrightGreen),
198 TokenKind::Whitespace => None,
199 TokenKind::Ident => None,
200 TokenKind::Eof => None,
201 TokenKind::Symbol(s) => match s.group() {
202 SymbolGroup::Operator => Some(Color::Cyan),
203 SymbolGroup::Keyword => Some(Color::Magenta),
204 SymbolGroup::Value => Some(Color::BrightCyan),
205 },
206 }
207}
208
209fn line_range_to_show(Span { start, end }: Span) -> (u32, u32) {
210 let lines = end.line - start.line + 1;
211
212 match lines {
213 1 => (start.line.checked_sub(2).unwrap_or(1).max(1), end.line + 2),
214 2 => ((start.line - 1).max(1), end.line + 2),
215 _ => ((start.line - 1).max(1), end.line + 1),
216 }
217}
218
219fn start_new_line(f: &mut fmt::Formatter<'_>, current_line: u32, width: usize) -> fmt::Result {
220 write!(f, " {:>width$} │ ", current_line, width = width)
221}
222
223fn print_line_skip(f: &mut fmt::Formatter<'_>, width: usize) -> fmt::Result {
224 write!(f, "╶{:╴>width$}┼╴", "─", width = width + 1)
225}
226
227fn print_blank_margin(f: &mut fmt::Formatter<'_>, width: usize) -> fmt::Result {
228 write!(f, " {:>width$}", " ", width = width + 1)
229}
230
231fn print_first_line(f: &mut fmt::Formatter<'_>, width: usize) -> fmt::Result {
232 print_blank_margin(f, width)?;
233 writeln!(f, "┌")
234}
235
236fn print_last_line(f: &mut fmt::Formatter<'_>, width: usize) -> fmt::Result {
237 print_blank_margin(f, width)?;
238 writeln!(f, "└")
239}
240
241fn print_line_features(
242 f: &mut fmt::Formatter<'_>,
243 current_line: u32,
244 line: &str,
245 width: usize,
246 span: Span,
247) -> fmt::Result {
248 if current_line == span.start.line {
249 let leading_pos: usize = line
250 .chars()
251 .take((span.start.col - 1) as usize)
252 .map(char::len_utf8)
253 .sum();
254 let leading_spaces = line[..leading_pos].width();
255 print_blank_margin(f, width)?;
256 write!(f, "│ ")?;
257
258 if span.start.line == span.end.line {
259 let n = span.end.col - span.start.col + 1;
260 let len: usize = line[leading_pos..]
261 .chars()
262 .take(n as usize)
263 .map(char::len_utf8)
264 .sum();
265 let width = line[leading_pos..(leading_pos + len)].width();
266
267 match width {
268 0 => writeln!(f, "{:>col$}", "↖", col = leading_spaces + 1)?,
269 1 => writeln!(f, "{:>col$}", "─", col = leading_spaces + 1)?,
270 2 => writeln!(f, "{:>col$}┘", "└", col = leading_spaces + 1)?,
271 _ => writeln!(
272 f,
273 "{:>col$}{:─>width$}┘",
274 "└",
275 "─",
276 col = leading_spaces + 1,
277 width = width - 2
278 )?,
279 }
280 } else {
281 let width = line[leading_pos..].width().checked_sub(1);
282
283 match width {
284 None => writeln!(f, "{:>width$}╶╶", "└", width = line.width() + 1)?,
285 Some(0) => writeln!(f, "{:>col$}╶╶", "└", col = leading_spaces + 1)?,
286 Some(w) => writeln!(
287 f,
288 "{:>col$}{:─>width$}╶╶",
289 "└",
290 "─",
291 col = leading_spaces + 1,
292 width = w
293 )?,
294 }
295 }
296 } else if current_line == span.end.line {
297 print_blank_margin(f, width)?;
298 write!(f, "│╶╶")?;
299 let len = line.chars().take(span.end.col as usize).map(char::len_utf8).sum();
300 let width = line[..len].width();
301
302 match width {
303 0 => writeln!(f, "{:>col$}", "┘", col = width)?,
304 _ => writeln!(f, "{:─>width$}┘", "─", width = width - 1)?,
305 }
306 }
307
308 Ok(())
309}
310
311fn num_digits(n: u32) -> usize {
312 ((n as f64).log10() + 1f64) as usize
313}
314
315fn remove_trailing_newline(s: &str) -> &str {
316 let tail = match s.get((s.len() - 2)..) {
317 Some(tail) => tail,
318 None => return s,
319 };
320
321 if tail == "\r\n" || tail == "\n\r" {
322 &s[..(s.len() - 2)]
323 } else if &tail[1..] == "\r" || &tail[1..] == "\n" {
324 &s[..(s.len() - 1)]
325 } else {
326 s
327 }
328}
329
330fn display_source_for_span(f: &mut fmt::Formatter<'_>, span: Span, buf: &str) -> fmt::Result {
331 let buf = remove_trailing_newline(buf);
332 let mut lines = LineIter::new(buf);
333 let (start, end) = line_range_to_show(span);
334 let width = num_digits(end);
335 let cursor = InputCursor::new(buf);
336 let mut pos = cursor.pos();
337 let mut lexer = Lexer::new(cursor);
338 let mut current_line = 1;
339 let mut skip_written = false;
340 let write_skips = end - start + 1 > 5;
341
342 print_first_line(f, width)?;
343 start_new_line(f, start, width)?;
344
345 loop {
346 let token = match lexer.next() {
347 Some(Err(_)) => break,
348 None | Some(Ok(Token { value: TokenValue::Eof, .. })) => {
349 writeln!(f)?;
351 print_line_features(f, current_line, lines.next().unwrap(), width, span)?;
352 print_last_line(f, width)?;
353 return Ok(());
354 }
355 Some(Ok(token)) => token,
356 };
357
358 pos = lexer.cursor().pos();
359
360 if token.span.end.line < start {
361 if token.span.end.line != current_line {
362 for _ in current_line..token.span.end.line {
363 lines.next();
364 }
365
366 current_line = token.span.end.line;
367 }
368
369 continue;
370 }
371
372 let color = get_color_for_token(&token);
373 let mut first_line = true;
374
375 for (i, line) in
376 LineIter::new(lexer.cursor().substr(token.span.start..=token.span.end)).enumerate()
377 {
378 if i != 0 {
379 current_line += 1;
380 }
381
382 if current_line < start {
383 lines.next();
384 continue;
385 }
386
387 if current_line > start + 1 && current_line < end - 1 && write_skips {
388 if !skip_written {
389 writeln!(f)?;
390 print_line_skip(f, width)?;
391 skip_written = true;
392 }
393
394 continue;
395 }
396
397 if !first_line {
398 writeln!(f)?;
399 }
400
401 if current_line > end {
402 print_line_features(f, current_line - 1, lines.next().unwrap(), width, span)?;
403 print_last_line(f, width)?;
404 return Ok(());
405 }
406
407 if !first_line {
408 print_line_features(f, current_line - 1, lines.next().unwrap(), width, span)?;
409 start_new_line(f, current_line, width)?;
410 }
411
412 match color {
413 Some(color) => write!(f, "{}", line.color(color))?,
414 None => write!(f, "{}", line)?,
415 }
416
417 first_line = false;
418 }
419 }
420
421 let remaining = lexer.cursor().substr(pos..);
422
423 for (i, line) in LineIter::new(remaining).enumerate() {
424 if current_line < start {
425 lines.next();
426 continue;
427 }
428
429 if current_line > end {
430 break;
431 }
432
433 if current_line > start + 1 && current_line < end - 1 && write_skips {
434 if !skip_written {
435 print_line_skip(f, width)?;
436 skip_written = true;
437 }
438
439 lines.next();
440 continue;
441 }
442
443 if i != 0 {
444 print_line_features(f, current_line - 1, lines.next().unwrap(), width, span)?;
445 start_new_line(f, current_line, width)?;
446 }
447
448 writeln!(f, "{}", line)?;
449
450 current_line += 1;
451 }
452
453 print_line_features(f, current_line - 1, lines.next().unwrap(), width, span)?;
454 print_last_line(f, width)?;
455 Ok(())
456}