1
use std::fmt;
2
use std::io::IsTerminal;
3
use std::ops::Range;
4

            
5
use thiserror::Error;
6

            
7
#[derive(Debug, Clone)]
8
pub struct ParseErrorDetails {
9
    pub message: String,
10
    pub input: String,
11
    pub span: Range<usize>,
12
}
13

            
14
impl ParseErrorDetails {
15
1149
    pub fn new(message: String, input: String, span: Range<usize>) -> Self {
16
1149
        Self {
17
1149
            message,
18
1149
            input,
19
1149
            span,
20
1149
        }
21
1149
    }
22

            
23
214
    fn find_line_info(&self) -> (usize, usize, &str) {
24
214
        let offset = self.span.start.min(self.input.len());
25
214
        let before = &self.input[..offset];
26
214
        let line_num = before.matches('\n').count() + 1;
27
214
        let line_start = before.rfind('\n').map_or(0, |i| i + 1);
28
214
        let line_end = self.input[offset..]
29
214
            .find('\n')
30
214
            .map_or(self.input.len(), |i| offset + i);
31
214
        let line = &self.input[line_start..line_end];
32
214
        let col = offset - line_start;
33
214
        (line_num, col, line)
34
214
    }
35

            
36
214
    pub fn render(&self, use_color: bool) -> String {
37
214
        let (line_num, col, line) = self.find_line_info();
38
214
        let line_num_width = line_num.to_string().len().max(1);
39

            
40
214
        let (red, cyan, reset) = if use_color {
41
            ("\x1b[31m", "\x1b[36m", "\x1b[0m")
42
        } else {
43
214
            ("", "", "")
44
        };
45

            
46
214
        let mut output = String::new();
47
214
        output.push_str(&format!("{red}error:{reset} {}\n", self.message));
48
214
        output.push_str(&format!(
49
214
            "{cyan}{:>width$} |{reset}\n",
50
214
            "",
51
214
            width = line_num_width
52
214
        ));
53
214
        output.push_str(&format!(
54
214
            "{cyan}{line_num:>line_num_width$} |{reset} {line}\n"
55
214
        ));
56
214
        output.push_str(&format!(
57
214
            "{cyan}{:>width$} |{reset} {}{red}^{reset}\n",
58
214
            "",
59
214
            " ".repeat(col),
60
214
            width = line_num_width
61
214
        ));
62
214
        output
63
214
    }
64

            
65
    pub fn render_auto(&self) -> String {
66
        self.render(std::io::stderr().is_terminal())
67
    }
68
}
69

            
70
impl fmt::Display for ParseErrorDetails {
71
214
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72
214
        write!(f, "{}", self.render(false))
73
214
    }
74
}
75

            
76
#[derive(Error, Debug)]
77
pub enum Error {
78
    #[error("{0}")]
79
    Parse(ParseErrorDetails),
80

            
81
    #[error("Compile error: {0}")]
82
    Compile(String),
83

            
84
    #[error("Runtime error: {0}")]
85
    Runtime(String),
86

            
87
    #[error("Undefined symbol: {0}")]
88
    UndefinedSymbol(String),
89

            
90
    #[error("Type error: expected {expected}, got {actual}")]
91
    Type { expected: String, actual: String },
92

            
93
    #[error("Arity error: {name} expects {expected} arguments, got {actual}")]
94
    Arity {
95
        name: String,
96
        expected: usize,
97
        actual: usize,
98
    },
99
}
100

            
101
impl Error {
102
    #[must_use]
103
1149
    pub fn parse(message: String, input: &str, span: Range<usize>) -> Self {
104
1149
        Self::Parse(ParseErrorDetails::new(message, input.to_string(), span))
105
1149
    }
106

            
107
    #[must_use]
108
    pub fn render(&self, use_color: bool) -> String {
109
        match self {
110
            Error::Parse(details) => details.render(use_color),
111
            _ => self.to_string(),
112
        }
113
    }
114

            
115
    #[must_use]
116
    pub fn render_auto(&self) -> String {
117
        match self {
118
            Error::Parse(details) => details.render_auto(),
119
            _ => self.to_string(),
120
        }
121
    }
122
}
123

            
124
pub type Result<T> = std::result::Result<T, Error>;