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
103
    pub fn new(message: String, input: String, span: Range<usize>) -> Self {
16
103
        Self {
17
103
            message,
18
103
            input,
19
103
            span,
20
103
        }
21
103
    }
22

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

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

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

            
46
1
        let mut output = String::new();
47
1
        output.push_str(&format!("{red}error:{reset} {}\n", self.message));
48
1
        output.push_str(&format!(
49
1
            "{cyan}{:>width$} |{reset}\n",
50
1
            "",
51
1
            width = line_num_width
52
1
        ));
53
1
        output.push_str(&format!(
54
1
            "{cyan}{line_num:>line_num_width$} |{reset} {line}\n"
55
1
        ));
56
1
        output.push_str(&format!(
57
1
            "{cyan}{:>width$} |{reset} {}{red}^{reset}\n",
58
1
            "",
59
1
            " ".repeat(col),
60
1
            width = line_num_width
61
1
        ));
62
1
        output
63
1
    }
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
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72
1
        write!(f, "{}", self.render(false))
73
1
    }
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
103
    pub fn parse(message: String, input: &str, span: Range<usize>) -> Self {
104
103
        Self::Parse(ParseErrorDetails::new(message, input.to_string(), span))
105
103
    }
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>;