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
pub mod lower;
pub mod parser;
pub mod tokens;

use lalrpop_util::ParseError;
type Error<T> = ParseError<usize, T, String>;

pub fn make_error(
    script: &str,
    unexpected: &str,
    line_number: usize,
    column_number: usize,
) -> String {
    format!(
        "{WS} |
{line_number} | {line}
{WS} | {underline}
{WS} |
{WS} = unexpected `{unexpected}`",
        WS = " ".repeat(line_number.to_string().len()),
        line_number = line_number,
        line = script.lines().nth(line_number - 1).unwrap(),
        underline = format!(
            "{}^{}",
            " ".repeat(column_number),
            "-".repeat(unexpected.len() - 1)
        ),
        unexpected = unexpected
    )
}

fn get_line(script: &str, location: usize) -> (usize, String, usize) {
    let line_number = script[..location + 1].lines().count();
    let line = match script.lines().nth(line_number - 1) {
        Some(line) => line,
        None => {
            let lines = script.lines().collect::<Vec<&str>>();
            lines[lines.len() - 1]
        }
    };

    let column = {
        let mut current_column = 0;
        let location = 0;
        for ch in script[..location].chars() {
            current_column += 1;
            if ch == '\n' {
                current_column = 0;
            }
        }
        current_column
    };
    (line_number, String::from(line), column as usize)
}

pub fn format_error<T: core::fmt::Debug>(script: &str, err: Error<T>) -> String {
    match err {
        Error::InvalidToken { location } => {
            let (line_number, line, column) = get_line(script, location);
            make_error(script, &line, line_number, column)
        }
        Error::UnrecognizedEOF { location, .. } => {
            let (line_number, line, _) = get_line(script, location);
            make_error(script, "EOF", line_number, line.len())
        }
        Error::UnrecognizedToken { token, .. } => {
            let start = token.0;
            let end = token.2;

            let (line_number, _, column) = get_line(script, start);
            let unexpected = &script[start..end];
            make_error(script, unexpected, line_number, column)
        }
        Error::ExtraToken { token } => {
            let start = token.0;
            let end = token.2;

            let (line_number, _, column) = get_line(script, start);
            let unexpected = &script[start..end];

            make_error(script, unexpected, line_number, column)
        }
        Error::User { error } => format!(
            "  |\n? | {}\n  | {}\n  |\n  = unexpected compiling error",
            error,
            format!("^{}", "-".repeat(error.len() - 1))
        ),
    }
}