1use 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
17pub(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 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(¬e.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}