Skip to main content

abyss_core/parser/
diagnostics.rs

1use std::sync::Arc;
2
3use ariadne::{Color, Label, Report, ReportKind, Source};
4use chumsky::{
5    error::{Rich, RichReason},
6    span::Span as ChumskySpan,
7};
8
9use super::SimpleSpan;
10use super::helpers::LineMap;
11
12#[derive(Debug, Clone)]
13pub struct ParserDiagnostic {
14    pub title: String,
15    pub label: String,
16    pub span: SimpleSpan<usize>,
17    pub help: Option<String>,
18}
19
20pub fn convert_rich_error<'a, T, S>(
21    error: Rich<'a, T, S>,
22    _map: &Arc<LineMap>,
23    title: &str,
24) -> ParserDiagnostic
25where
26    T: std::fmt::Display + Clone,
27    S: ChumskySpan<Context = (), Offset = usize> + Clone,
28{
29    let label = match error.reason() {
30        RichReason::ExpectedFound { .. } => match error.found() {
31            Some(found) => format!("Unexpected token `{found}`"),
32            None => "Unexpected end of incantation".to_string(),
33        },
34        RichReason::Custom(msg) => msg.clone(),
35    };
36
37    let expected: Vec<String> = match error.reason() {
38        RichReason::ExpectedFound { expected, .. } => {
39            expected.iter().map(|pat| pat.to_string()).collect()
40        }
41        RichReason::Custom(_) => Vec::new(),
42    };
43
44    let help = if expected.is_empty() {
45        None
46    } else {
47        Some(format!("Perhaps you meant one of: {}", expected.join(", ")))
48    };
49
50    let span_source = error.span().clone();
51    let span = SimpleSpan::new(span_source.start(), span_source.end());
52
53    ParserDiagnostic {
54        title: title.to_string(),
55        label,
56        span,
57        help,
58    }
59}
60
61pub fn emit_diagnostics(
62    source_id: &str,
63    source: &str,
64    diagnostics: &[ParserDiagnostic],
65) -> Result<(), std::io::Error> {
66    for diagnostic in diagnostics {
67        let span_range = diagnostic.span.into_range();
68        let mut report = Report::build(ReportKind::Error, (source_id, span_range.clone()))
69            .with_message(&diagnostic.title)
70            .with_label(
71                Label::new((source_id, span_range))
72                    .with_message(diagnostic.label.clone())
73                    .with_color(Color::Red),
74            );
75
76        if let Some(help) = &diagnostic.help {
77            report = report.with_help(help.clone());
78        }
79
80        report.finish().print((source_id, Source::from(source)))?;
81    }
82    Ok(())
83}