Skip to main content

cobble/
error.rs

1use crate::diagnostics::{
2    byte_offset_for_line_column, DiagnosticSeverity, FileSourceDiagnostics, SourceDiagnostic,
3};
4use ariadne::{Color, Label, Report, ReportKind, Source};
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum CobbleError {
9    #[error("Parse error")]
10    ParseError(String),
11
12    #[error("IO error: {0}")]
13    IoError(#[from] std::io::Error),
14
15    #[error("Transpilation error: {0}")]
16    TranspileError(String),
17}
18
19pub fn report_error(filename: &str, src: &str, error: &CobbleError) {
20    match error {
21        CobbleError::ParseError(msg) => {
22            Report::build(ReportKind::Error, (filename, 0..1))
23                .with_message("Parse Error")
24                .with_label(
25                    Label::new((filename, 0..1))
26                        .with_message(msg.clone())
27                        .with_color(Color::Red),
28                )
29                .finish()
30                .print((filename, Source::from(src)))
31                .unwrap();
32        }
33        CobbleError::TranspileError(msg) => {
34            Report::build(ReportKind::Error, (filename, 0..1))
35                .with_message("Transpilation Error")
36                .with_label(
37                    Label::new((filename, 0..1))
38                        .with_message(msg.clone())
39                        .with_color(Color::Red),
40                )
41                .finish()
42                .print((filename, Source::from(src)))
43                .unwrap();
44        }
45        CobbleError::IoError(e) => {
46            eprintln!("IO Error: {}", e);
47        }
48    }
49}
50
51/// Report parse errors with ariadne (for token-based parsing)
52pub fn report_parse_errors(filename: &str, src: &str, errors: &[String]) {
53    for error in errors {
54        eprintln!("Parse error in {}: {}", filename, error);
55    }
56
57    // If there's only one error, try to provide a better report
58    if errors.len() == 1 {
59        Report::build(ReportKind::Error, (filename, 0..src.len().min(1)))
60            .with_message("Parse Error")
61            .with_note(&errors[0])
62            .finish()
63            .eprint((filename, Source::from(src)))
64            .ok();
65    }
66}
67
68pub fn report_source_diagnostics(filename: &str, src: &str, diagnostics: &[SourceDiagnostic]) {
69    for diagnostic in diagnostics {
70        let start = byte_offset_for_line_column(src, diagnostic.line, diagnostic.column);
71        let end = (start + 1).min(src.len());
72        let color = match diagnostic.severity {
73            DiagnosticSeverity::Error => Color::Red,
74            DiagnosticSeverity::Warning => Color::Yellow,
75        };
76        let report_kind = match diagnostic.severity {
77            DiagnosticSeverity::Error => ReportKind::Error,
78            DiagnosticSeverity::Warning => ReportKind::Warning,
79        };
80
81        let mut report = Report::build(report_kind, (filename, start..end))
82            .with_message(format!("{}: {}", diagnostic.kind, diagnostic.message))
83            .with_label(
84                Label::new((filename, start..end))
85                    .with_message(diagnostic.message.clone())
86                    .with_color(color),
87            );
88
89        if let Some(help) = &diagnostic.help {
90            report = report.with_note(help);
91        }
92
93        report.finish().eprint((filename, Source::from(src))).ok();
94    }
95}
96
97pub fn report_file_source_diagnostics(diagnostics: &[FileSourceDiagnostics]) {
98    for file_diagnostics in diagnostics {
99        let filename = file_diagnostics.path.to_string_lossy();
100        report_source_diagnostics(
101            &filename,
102            &file_diagnostics.source,
103            &file_diagnostics.diagnostics,
104        );
105    }
106}