Skip to main content

hatter/
error.rs

1#![allow(unused_macros)]
2use {
3    crate::Jump,
4    std::{error, fmt, io, num},
5};
6
7/// What kind of error?
8#[derive(Debug, PartialEq)]
9pub enum ErrorKind {
10    SyntaxError,
11    ParseError,
12    RuntimeError,
13
14    ArgNotFound,
15    WrongArgType,
16
17    Jump(Jump),
18}
19
20/// Usually source-related.
21#[derive(Debug, PartialEq)]
22pub struct Error {
23    pub kind: ErrorKind,
24    pub details: String,
25    pub pos: usize,
26    pub len: usize,
27}
28
29impl Error {
30    pub fn new(kind: ErrorKind, details: String, pos: usize, len: usize) -> Error {
31        Error {
32            kind,
33            details,
34            pos,
35            len,
36        }
37    }
38}
39
40impl error::Error for Error {
41    fn description(&self) -> &str {
42        &self.details
43    }
44}
45
46impl fmt::Display for Error {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "Error: {}", self.details)
49    }
50}
51
52impl From<num::ParseIntError> for Error {
53    fn from(error: num::ParseIntError) -> Self {
54        Error {
55            kind: ErrorKind::ParseError,
56            details: format!("{}", error),
57            pos: 0,
58            len: 0,
59        }
60    }
61}
62
63impl From<io::Error> for Error {
64    fn from(error: io::Error) -> Self {
65        Error {
66            kind: ErrorKind::RuntimeError,
67            details: format!("{}", error),
68            pos: 0,
69            len: 0,
70        }
71    }
72}
73
74impl From<Error> for io::Error {
75    fn from(error: Error) -> Self {
76        io::Error::new(io::ErrorKind::Other, error.details)
77    }
78}
79
80/// Create an error with line and col information.
81macro_rules! scan_error {
82    ($pos:expr, $len:expr, $msg:expr) => {{
83        use crate::{Error, ErrorKind};
84        Err(Error::new(ErrorKind::SyntaxError, $msg.into(), $pos, $len))
85    }};
86    ($pos:expr, $len:expr, $msg:expr, $($args:expr),+) => {
87        scan_error!($pos, $len, format!($msg, $($args),*));
88    };
89}
90
91/// Create an error at a position in the source.
92macro_rules! pos_error {
93    ($pos:expr, $msg:expr) => {{
94        use crate::{Error, ErrorKind};
95        Err(Error::new(ErrorKind::ParseError, $msg.into(), $pos, 1))
96    }};
97    ($pos:expr, $msg:expr, $($args:expr),+) => {
98        pos_error!($pos, format!($msg, $($args),+));
99    };
100}
101
102/// Create a Jump (break, continue, etc)
103macro_rules! jump {
104    ($jump:expr) => {{
105        use crate::{Error, ErrorKind};
106        Err(Error::new(ErrorKind::Jump($jump), "".to_string(), 0, 0))
107    }};
108}
109
110/// Convenient way to create an error of ErrorKind.
111macro_rules! error_kind {
112    ($kind:ident, $msg:expr) => {{
113        use crate::{Error, ErrorKind};
114        Error::new(ErrorKind::$kind, $msg.into(), 0, 0)
115    }};
116    ($kind:ident, $msg:expr, $($args:expr),*) => {
117        error_kind!($kind, format!($msg, $($args),*));
118    };
119}
120
121/// Convenient way to create an Err(Error{}).
122macro_rules! error {
123    ($msg:expr) => {{
124        use crate::{Error, ErrorKind};
125        Err(Error::new(ErrorKind::RuntimeError, $msg.into(), 0, 0))
126    }};
127    ($msg:expr, $($args:expr),*) => {
128        error!(format!($msg, $($args),*));
129    };
130}
131
132/// Pretty-print an error message, complete with colors and the line
133/// in question.
134pub fn print_error<P: AsRef<std::path::Path>, S: AsRef<str>>(path: P, source: S, err: Error) {
135    let path = path.as_ref();
136    let source = source.as_ref();
137    let (red, blue, _gold, clear) = if std::env::var("NO_COLOR").is_ok() {
138        ("", "", "", "")
139    } else {
140        ("\x1b[91m", "\x1b[1;94m", "\x1b[1;93m", "\x1b[0m")
141    };
142
143    let (line, col) = line_and_col(source, err.pos);
144    // println!("(line, col) = {:?}", (line, col));
145    println!(
146        " {}-->{} {}:{}:{}",
147        blue,
148        clear,
149        path.to_str().unwrap(),
150        line,
151        col
152    );
153    println!("   {}|{}", blue, clear);
154
155    let lines = source.split('\n');
156    let pline = |num| {
157        if let Some(line) = lines.clone().nth(num) {
158            println!("{}   |{} {}", blue, clear, line)
159        }
160    };
161
162    if line > 2 {
163        pline(line - 3);
164    }
165    if line > 1 {
166        pline(line - 2);
167    }
168    println!(
169        "{}{: <3}|{} {}",
170        blue,
171        line,
172        clear,
173        lines.clone().nth(line - 1).unwrap()
174    );
175    println!(
176        "   {}|{} {}{} {}{}",
177        blue,
178        red,
179        " ".repeat(if col > 0 { col - 1 } else { 0 }),
180        "^".repeat(std::cmp::min(err.len, 20)),
181        err.details,
182        clear
183    );
184    pline(line);
185    pline(line + 1);
186}
187
188/// Calculate line # and col position for a position in a source file.
189pub fn line_and_col(source: &str, pos: usize) -> (usize, usize) {
190    let mut line = 1;
191    let mut col = 0;
192    for (i, c) in source.chars().enumerate() {
193        if c == '\n' {
194            if i == pos {
195                return (line, col + 1);
196            }
197            line += 1;
198            col = 0;
199        } else {
200            col += 1;
201        }
202        if i >= pos {
203            break;
204        }
205    }
206    (line, col)
207}