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
106
107
108
109
pub mod layout;
pub mod pretty;
mod util;

use tan::error::{Error, ErrorNote};

// #TODO reuse the Position from tan?
// #TODO split into `format_expr`, `format_error`.
// #TODO add special support for formatting multiple errors?

pub fn format_error_note_pretty(note: &ErrorNote, input: &str) -> String {
    let Some(range) = &note.range else {
        return format!("{}", note.text);
    };

    // #TODO do this once, outside of this function!
    // #TODO can we reuse the position line/col?

    let chars = input.chars();

    let mut index: usize = 0;
    let mut line = 0;
    let mut line_start: usize = 0;
    let mut line_str = String::new();

    for c in chars {
        index += 1;

        if c == '\n' {
            if index > range.start.index {
                break;
            }

            line += 1;
            line_start = index;

            line_str.clear();

            continue;
        }

        line_str.push(c);
    }

    let line_space = " ".repeat(format!("{}", line + 1).len());

    let len = range.end.index - range.start.index;

    // let indicator = if len == 1 {
    //     "^--- near here".to_owned()
    // } else {
    //     "^".repeat(len)
    // };

    // #TODO use `^` or `-` depending on note importance, like Rust.

    let indicator = "^".repeat(len);

    let col = range.start.index - line_start; // #TODO range.start.col
    let indicator_space = " ".repeat(col);

    format!(
        "{}|\n{}| {}\n{}|{} {} {}",
        line_space,
        line + 1,
        line_str,
        line_space,
        indicator_space,
        indicator,
        note.text,
    )
}

pub fn format_error(error: &Error) -> String {
    format!("{}\n", error.kind())
}

// #TODO also format error without input.
// #TODO implement this in ...Tan :)
// #TODO format the error as symbolic expression.
// #TODO format the error as JSON.
// #TODO make more beautiful than Rust.
// #TODO add as method to Ranged<E: Error>? e.g. `format_pretty`
pub fn format_error_pretty(error: &Error, input: &str) -> String {
    let Some(note) = error.notes.first() else {
        return format!("{}\n at {}", error.kind(), error.file_path);
    };

    let prologue = if let Some(range) = &note.range {
        let position = &range.start;
        format!(
            "{}\n at {}:{}:{}",
            error.kind(),
            error.file_path,
            position.line + 1,
            position.col + 1,
        )
    } else {
        format!("{}\n at {}", error.kind(), error.file_path)
    };

    let notes: Vec<String> = error
        .notes
        .iter()
        .map(|note| format_error_note_pretty(note, input))
        .collect();

    format!("{prologue}\n{}", notes.join("\n"))
}