1use std::fmt;
2
3use crate::ast::Span;
4
5#[derive(Debug, Clone)]
7pub struct ParseError {
8 pub message: String,
9 pub span: Span,
10 pub kind: ErrorKind,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ErrorKind {
15 UnexpectedChar(char),
16 UnexpectedEof,
17 MixedOperators,
18 UnclosedGroup,
19 UnclosedString,
20 InvalidUnit(String),
21 InvalidNumber,
22 EmptyGroup,
23 TrailingInput,
24 InvalidUnitForSplit { unit: String, split: String },
25}
26
27impl ParseError {
28 pub fn format_with_source(&self, source: &str) -> String {
29 let (line, column) = byte_offset_to_position(source, self.span.start);
30 let source_line = source.lines().nth(line - 1).unwrap_or("");
31
32 let mut output = format!("error: {}\n", self.message);
33 output.push_str(&format!(" --> input:{}:{}\n", line, column));
34 output.push_str(" |\n");
35 output.push_str(&format!("{:>3} | {}\n", line, source_line));
36 output.push_str(" | ");
37
38 let line_start = source[..self.span.start]
40 .rfind('\n')
41 .map(|position| position + 1)
42 .unwrap_or(0);
43 let offset_in_line = self.span.start - line_start;
44 for _ in 0..offset_in_line {
45 output.push(' ');
46 }
47 let caret_len = (self.span.end - self.span.start).max(1);
48 for _ in 0..caret_len {
49 output.push('^');
50 }
51 output.push('\n');
52 output
53 }
54}
55
56impl fmt::Display for ParseError {
57 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(formatter, "{}", self.message)
59 }
60}
61
62impl std::error::Error for ParseError {}
63
64fn byte_offset_to_position(source: &str, offset: usize) -> (usize, usize) {
65 let before = &source[..offset.min(source.len())];
66 let line = before
67 .chars()
68 .filter(|&character| character == '\n')
69 .count()
70 + 1;
71 let column = before
72 .rfind('\n')
73 .map(|position| offset - position)
74 .unwrap_or(offset + 1);
75 (line, column)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn byte_offset_single_line() {
84 assert_eq!(byte_offset_to_position("(a|b/c)", 4), (1, 5));
85 }
86
87 #[test]
88 fn byte_offset_multiline() {
89 assert_eq!(byte_offset_to_position("abc\ndef\nghi", 5), (2, 2));
90 }
91}