Skip to main content

nifty_cli/
report.rs

1//! Renders diagnostics as annotated source snippets on stderr.
2
3use std::io::{self, IsTerminal};
4
5use ariadne::{Config, IndexType, Label, Report, ReportKind};
6use nifty_interp::RuntimeError;
7use nifty_syntax::{Diagnostic, Span};
8
9use crate::error::CliError;
10use crate::frontend::Sources;
11
12pub(crate) fn print(sources: &Sources, diagnostics: &[Diagnostic]) -> Result<(), CliError> {
13    write(sources, diagnostics)
14        .map_err(|error| CliError::new(format!("can't write diagnostics: {error}")))
15}
16
17/// Reports an error the app hit while running, pointing at the code responsible.
18pub(crate) fn runtime_error(sources: &Sources, error: &RuntimeError) {
19    let diagnostic = Diagnostic::new(&error.message, error.span);
20    if let Err(write_error) = print(sources, &[diagnostic]) {
21        eprintln!("error: {}", error.message);
22        eprintln!("(and the full report couldn't be written: {write_error})");
23    }
24}
25
26fn write(sources: &Sources, diagnostics: &[Diagnostic]) -> io::Result<()> {
27    // Nifty spans are byte offsets; ariadne counts characters unless told otherwise.
28    let config = Config::default()
29        .with_index_type(IndexType::Byte)
30        .with_color(io::stderr().is_terminal());
31    let mut cache = ariadne::sources(
32        sources
33            .files()
34            .iter()
35            .map(|file| (file.name(), file.text.as_str())),
36    );
37    let place = |span: Span| (sources.file(span.file).name(), span.range());
38    for diagnostic in diagnostics {
39        let notes = diagnostic
40            .notes
41            .iter()
42            .map(|note| Label::new(place(note.span)).with_message(&note.message));
43        Report::build(ReportKind::Error, place(diagnostic.span))
44            .with_config(config)
45            .with_message(&diagnostic.message)
46            .with_label(Label::new(place(diagnostic.span)))
47            .with_labels(notes)
48            .finish()
49            .eprint(&mut cache)?;
50    }
51    let count = diagnostics.len();
52    eprintln!("{count} error{}", if count == 1 { "" } else { "s" });
53    Ok(())
54}