sphinx/
frontend.rs

1//! output/error reporting and formatting
2
3use core::iter;
4use core::fmt::{self, Formatter};
5use std::error::Error;
6
7use crate::utils;
8use crate::debug::SourceError;
9use crate::debug::symbol::{ResolvedSymbol, DebugSymbolResolver};
10
11pub fn print_source_errors<E>(resolver: &impl DebugSymbolResolver, errors: &[E]) where E: SourceError {
12    let symbols = errors.iter().filter_map(|err| err.debug_symbol());
13    
14    let resolved_table = resolver.resolve_symbols(symbols).unwrap();
15    
16    // resolve errors and collect into vec
17    let mut render_errors = errors.iter().filter_map(
18        |error| match error.debug_symbol() {
19            None => Some(RenderError(error, None)),
20            Some(symbol) => {
21                let resolved = resolved_table.lookup(symbol).unwrap();
22                match resolved {
23                    Ok(resolved) => Some(RenderError(error, Some(resolved))),
24                    Err(resolve_error) => {
25                        println!("{}", error);
26                        println!("Could not resolve symbol: {}", resolve_error);
27                        None
28                    }
29                }
30            },
31        })
32        .collect::<Vec<RenderError<E>>>();
33    
34    // sort errors by line number
35    render_errors.sort_by_key(|render| render.1.map_or_else(
36        || (1, 0), |resolved| (0, resolved.lineno())
37    ));
38    
39    for render in render_errors.iter() {
40        println!("{}", render);
41    }
42}
43
44
45pub struct RenderError<'e, 's, E>(pub &'e E, pub Option<&'s ResolvedSymbol>) where E: Error;
46
47impl<E> fmt::Display for RenderError<'_, '_, E> where E: Error {
48    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let RenderError(error, source_lines) = self;
50        
51        if let Some(source_lines) = source_lines {
52            write!(fmt, "{}.\n\n{}", error, source_lines)
53        } else {
54            write!(fmt, "{}.", error)
55        }
56    }
57}
58
59impl fmt::Display for ResolvedSymbol {
60    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
61        fmt_source_lines(fmt, self)
62    }
63}
64
65fn fmt_source_lines(fmt: &mut Formatter<'_>, symbol: &ResolvedSymbol) -> fmt::Result {
66    let mut start_idx = 0;
67    for (num, raw_line) in symbol.iter_whole_lines().enumerate() {
68        let end_index = start_idx + raw_line.len(); // of current line
69        
70        let margin = format!("{: >3}", num + symbol.lineno());
71        let source_line = raw_line.trim_end();
72        
73        let start_col =
74            if symbol.start() < start_idx { 0 } // started on a previous line
75            else { symbol.start() };
76        
77        let end_col =
78            if symbol.end() > end_index { source_line.len() } // ends on a next line
79            else { symbol.end() - start_idx };
80        
81        let mut marker = String::new();
82        marker.extend(iter::repeat(' ').take(margin.len()));
83        marker.push_str("     ");
84        
85        marker.extend(iter::repeat(' ').take(start_col));
86        marker.extend(iter::repeat('^').take(usize::max(end_col - start_col, 1))); // for single index symbols
87        
88        writeln!(fmt, "{}|    {}", margin, source_line)?;
89        writeln!(fmt, "{}", marker)?;
90        
91        start_idx += raw_line.len();
92    }
93    
94    Ok(())
95}
96
97fn fmt_source_line_single(fmt: &mut Formatter<'_>, symbol: &ResolvedSymbol) -> fmt::Result {
98    let margin = format!("{: >3}|    ", symbol.lineno());
99    let source_line = render_symbol_single_line(symbol).to_string();
100    
101    let start_col = symbol.start_col();
102    let end_col =
103        if !symbol.is_multiline() {
104            symbol.end_col()
105        } else {
106            source_line.len()
107        };
108    
109    let mut marker = String::new();
110    marker.extend(iter::repeat(' ').take(margin.len() + start_col));
111    marker.extend(iter::repeat('^').take(usize::max(end_col - start_col, 1)));
112    
113    writeln!(fmt, "{}{}", margin, source_line)?;
114    writeln!(fmt, "{}\n", marker)?;
115    
116    Ok(())
117}
118
119
120fn render_symbol_lines(symbol: &ResolvedSymbol) -> impl fmt::Display + '_ {
121    utils::make_display(|fmt| fmt_symbol_lines(fmt, symbol))
122}
123
124// format a symbol, including the surrounding text on the same lines, and trim trailing whitespace
125fn fmt_symbol_lines(fmt: &mut Formatter<'_>, symbol: &ResolvedSymbol) -> fmt::Result {
126    for line in symbol.iter_whole_lines() {
127        fmt.write_str(line.trim_end())?;
128        fmt.write_str("\n")?;
129    }
130    Ok(())
131}
132
133// if the symbol isn't multiline, this produces the same output as render_symbol_lines()
134// if it is multiline, this renders the first line of the symbol followed by "..."
135fn render_symbol_single_line(symbol: &ResolvedSymbol) -> impl fmt::Display + '_ {
136    utils::make_display(|fmt| fmt_symbol_single_line(fmt, symbol))
137}
138
139fn fmt_symbol_single_line(fmt: &mut fmt::Formatter<'_>, symbol: &ResolvedSymbol) -> fmt::Result { 
140    if let Some(first_line) = symbol.iter_whole_lines().next() {
141        if symbol.is_multiline() {
142            write!(fmt, "{}...", first_line.trim_end())?;
143        } else {
144            fmt.write_str(first_line.trim_end())?;
145        }
146    }
147    Ok(())
148}