1const RED: &str = "\x1b[31;1m";
2const NORMAL: &str = "\x1b[0m";
3const BLUE: &str = "\x1b[34;1m";
4const BOLD: &str = "\x1b[1m";
5
6#[derive(Debug)]
7pub enum ErrorKind {
8 ExpectedValue(&'static str),
9 Expected(&'static str),
10 NegPredFailed(&'static str),
11 All(Vec<Error>)
12}
13
14impl std::fmt::Display for ErrorKind {
15 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
16 match self {
17 ErrorKind::ExpectedValue(expected) => write!(f, "Expected value: {expected}"),
18 ErrorKind::Expected(expected) => write!(f, "Expected: {expected}"),
19 ErrorKind::NegPredFailed(not_expected) => write!(f, "Negated predicate failed: {not_expected}"),
20 ErrorKind::All(errors) => write!(f, "All {} accepted patterns fail to match", errors.len()),
21 }
22 }
23}
24
25#[derive(Debug)]
26pub struct Error {
27 kind: ErrorKind,
28 remaining_bytes: usize,
29 trace: Vec<String>,
30 note: Option<String>,
31}
32
33impl Error {
34 pub fn new(kind: ErrorKind, input: &str, root: impl Into<String>) -> Error {
35 Error {
36 kind,
37 remaining_bytes: input.len(),
38 trace: vec![root.into()],
39 note: None,
40 }
41 }
42
43 pub fn with_trace(mut self, trace: impl Into<String>) -> Self {
44 self.trace.push(trace.into());
45 self
46 }
47
48 pub fn with_note(mut self, note: impl Into<String>) -> Self {
49 if self.note.is_none() {
50 self.note = Some(note.into());
51 }
52 self
53 }
54
55 pub fn print(&self, input: &str) {
56 if self.remaining_bytes > input.len() {
57 panic!("Error::print: remaining_bytes is greater than input length");
58 }
59 let position = input.len() - self.remaining_bytes;
60
61 let line_start = input[..position].rfind('\n').map(|i| i + 1).unwrap_or(0);
62 let line_end = input[position..].find('\n').map(|i| i + position).unwrap_or(input.len());
63 let line_number = input[..position].matches('\n').count() + 1;
64 let position_in_utf8_line = input[line_start..position].chars().count();
65
66 println!("{RED}error{NORMAL}: {}", self.kind);
67 println!("{BLUE} -->{NORMAL} {}:{}:{}", self.trace[0], line_number, position - line_start + 1);
68 println!("{BLUE} |{NORMAL}");
69 println!("{BLUE}{:<3}|{NORMAL} {}", line_number, &input[line_start..line_end]);
70 println!("{BLUE} |{NORMAL} {}{RED}^{NORMAL}", " ".repeat(position_in_utf8_line));
71 if let Some(note) = &self.note {
72 println!(" {BLUE}= {NORMAL}{BOLD}note{NORMAL}: {note}");
73 }
74 println!(" {BLUE}= {NORMAL}{BOLD}trace{NORMAL}: {}", self.trace.join(", "));
75 }
76
77 pub fn into_pest<Rule: pest::RuleType>(self, input: &str) -> pest::error::Error<Rule> {
78 pest::error::Error::new_from_pos(
79 pest::error::ErrorVariant::CustomError {
80 message: format!("{}", self.kind),
81 },
82 pest::Position::new(input, input.len() - self.remaining_bytes).unwrap()
83 )
84 }
85}