abyss_core/parser/
diagnostics.rs1use 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}