summaryrefslogtreecommitdiff
path: root/src/reports/mod.rs
blob: bbddba98889b3646799cd9c1dd3dc9d4dc27ed63 (plain) (blame)
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
110
111
112
113
114
115
116
117
118
119
120
121
mod resolver_error;
mod source_hierarchy;
mod source_symbols;
mod unused_symbols;

pub use resolver_error::*;
pub use source_hierarchy::*;
pub use source_symbols::*;
pub use unused_symbols::*;

use crate::*;

pub use log::LogLevel;
pub use ansi::*;


pub fn report_source_issue(level: LogLevel, context: &Context, message: &str) {
    let mut reporter = SourceIssueReporter::new(context, message, level);
    reporter.format_source_span(context.source);
    let string = reporter.output;

    // Print the completed message.
    match level {
        LogLevel::Info  => log::info!( "{string}"),
        LogLevel::Warn  => log::warn!( "{string}"),
        LogLevel::Error => log::error!("{string}"),
        LogLevel::Fatal => log::fatal!("{string}"),
    }
}


struct SourceIssueReporter<'a> {
    context: &'a Context<'a>,
    output: String,
    level: LogLevel,
    digits: usize,
}

impl<'a> SourceIssueReporter<'a> {
    pub fn new(context: &'a Context<'a>, message: &str, level: LogLevel) -> Self {
        let output = format!("{message}\n");
        let digits = get_end_line_number(context.source).to_string().len();
        Self { context, level, output, digits }
    }

    pub fn format_source_span(&mut self, source: &SourceSpan) {
        let colour = match self.level {
            LogLevel::Info => BLUE,
            LogLevel::Warn => YELLOW,
            LogLevel::Error => RED,
            LogLevel::Fatal => RED,
        };

        // Print location line.
        self.output.push_str(NORMAL);
        let arrow = "-->";
        let location = source.location();
        self.push(&format!("{BLUE}{arrow:>w$}{NORMAL} {location}\n", w=self.digits+3));

        let line_count = location.end.line - location.start.line + 1;
        for line_i in 0..line_count {
            let merged_i = source.in_merged.start.line + line_i;
            let location_i = location.start.line + line_i;
            // Format source context.
            let source_line = self.context.source_code.split('\n').nth(merged_i)
                .unwrap_or("<error reading line from source>");
            let left = match line_i == 0 {
                true => location.start.column,
                false => 0,
            };
            let right = match line_i + 1 == line_count {
                true => location.end.column + 1,
                false => source_line.chars().count(),
            };

            // Print source code line.
            self.push(&format!("{BLUE} {line_num:>w$} | {NORMAL}", line_num=location_i+1, w=self.digits));
            for (i, c) in source_line.chars().enumerate() {
                if i == left  { self.output.push_str(colour) }
                if i == right { self.output.push_str(NORMAL) }
                self.output.push(c);
            }
            self.output.push_str(NORMAL);
            self.output.push('\n');

            // Print an underline on the final line.
            if line_i + 1 == line_count {
                // Print source code underline.
                let space = " ";
                self.push(&format!("{BLUE} {space:>w$} | {NORMAL}", w=self.digits));
                for _ in 0..left { self.output.push_str(" "); }
                self.output.push_str(colour);
                for _ in left..right { self.output.push_str("^"); }
                self.output.push_str(NORMAL);
                self.output.push('\n');
            }
        }

        // Recurse.
        if let Some(child) = &source.child {
            self.format_source_span(&child);
        }

        // Remove the trailing new-line.
        self.output.pop();
    }

    fn push(&mut self, string: &str) {
        self.output.push_str(&string);
    }
}


// Return the highest 1-based end line number of any location in the chain.
fn get_end_line_number(source: &SourceSpan) -> usize {
    let end_line_number = source.location().end.line + 1;
    match &source.child {
        Some(child) => std::cmp::max(end_line_number, get_end_line_number(&child)),
        None => end_line_number,
    }
}