about summary refs log tree commit diff
path: root/src/error.rs
blob: ed923552473ad2d4eca837ec30768d0a50257b3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use crate::{
    interpret::walker::WalkerError,
    lex::token::Location,
    parse::parser::{ParserError, ParserErrorLocation},
};
use colored::Colorize;
use std::fmt::Display;

pub enum RHError {
    Parse(ParserError),
    Run(WalkerError),
}

impl RHError {
    pub fn hydrate_source<'s>(self, source: &'s str) -> RHErrorWithSource<'s> {
        RHErrorWithSource {
            error: self,
            source,
        }
    }
}

pub struct RHErrorWithSource<'source> {
    error: RHError,
    source: &'source str,
}

impl Display for RHErrorWithSource<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let (error_title, error_string) = match &self.error {
            RHError::Parse(parser_error) => (
                "Encountered error during parsing",
                format!("{}", parser_error.kind),
            ),
            RHError::Run(walker_error) => (
                "Encountered runtime error",
                format!("{}", walker_error.kind),
            ),
        };

        let at = MappedErrorLocation::new(self);

        let arrow_pad_len = at.line_column + 3 + at.line_number.to_string().len();

        writeln!(f, "{} {}:\n", " Error ".on_red(), error_title)?;
        writeln!(
            f,
            "{}{}",
            format!("{} | ", at.line_number).dimmed(),
            at.source_line
        )?;
        writeln!(f, "{}{}", " ".repeat(arrow_pad_len), "^".red())?;
        writeln!(f, "{}", error_string)?;

        Ok(())
    }
}

pub struct MappedErrorLocation<'s> {
    line_number: usize,
    line_column: usize,
    source_line: &'s str,
}

impl<'s> MappedErrorLocation<'s> {
    const SOURCE_MISSING_ERROR_LOCATION: &'static str =
        "Found error but could not get it's location in source, how does this even happen?";

    fn new(from: &'s RHErrorWithSource<'s>) -> MappedErrorLocation<'s> {
        match &from.error {
            RHError::Parse(error) => match &error.at {
                ParserErrorLocation::Specific(at) => Self::at_location(from, at),
                ParserErrorLocation::Eof => Self::last_line(from),
            },
            RHError::Run(error) => Self::at_location(from, &error.at),
        }
    }

    fn last_line(from: &'s RHErrorWithSource<'s>) -> MappedErrorLocation<'s> {
        let lines = from.source.lines();
        let (line_count, _) = lines.size_hint();
        let last_line = lines.last().expect(Self::SOURCE_MISSING_ERROR_LOCATION);

        MappedErrorLocation {
            line_number: line_count,
            line_column: last_line.len(),
            source_line: last_line,
        }
    }

    fn at_location(
        from: &'s RHErrorWithSource<'s>,
        location: &'s Location,
    ) -> MappedErrorLocation<'s> {
        MappedErrorLocation {
            line_number: location.row,
            line_column: location.col,
            source_line: from
                .source
                .lines()
                .nth(location.row)
                .expect(Self::SOURCE_MISSING_ERROR_LOCATION),
        }
    }
}