1use crate::ast::Span;
2use ariadne::{Color, Label, Report, ReportKind, Source};
3use std::fmt;
4use std::sync::Arc;
5
6#[derive(Debug, Clone)]
8pub struct ErrorDetails {
9 pub message: String,
10 pub span: Span,
11 pub source_id: String,
12 pub source_text: Arc<str>,
13 pub doc_name: String,
14 pub doc_start_line: usize,
15 pub suggestion: Option<String>,
16}
17
18#[derive(Debug, Clone)]
20pub enum LemmaError {
21 Parse(Box<ErrorDetails>),
23
24 Semantic(Box<ErrorDetails>),
26
27 Runtime(Box<ErrorDetails>),
29
30 Engine(String),
32
33 CircularDependency(String),
35
36 MultipleErrors(Vec<LemmaError>),
38}
39
40impl LemmaError {
41 pub fn parse(
43 message: impl Into<String>,
44 span: Span,
45 source_id: impl Into<String>,
46 source_text: Arc<str>,
47 doc_name: impl Into<String>,
48 doc_start_line: usize,
49 ) -> Self {
50 Self::Parse(Box::new(ErrorDetails {
51 message: message.into(),
52 span,
53 source_id: source_id.into(),
54 source_text,
55 doc_name: doc_name.into(),
56 doc_start_line,
57 suggestion: None,
58 }))
59 }
60
61 pub fn parse_with_suggestion(
63 message: impl Into<String>,
64 span: Span,
65 source_id: impl Into<String>,
66 source_text: Arc<str>,
67 doc_name: impl Into<String>,
68 doc_start_line: usize,
69 suggestion: impl Into<String>,
70 ) -> Self {
71 Self::Parse(Box::new(ErrorDetails {
72 message: message.into(),
73 span,
74 source_id: source_id.into(),
75 source_text,
76 doc_name: doc_name.into(),
77 doc_start_line,
78 suggestion: Some(suggestion.into()),
79 }))
80 }
81
82 pub fn semantic(
84 message: impl Into<String>,
85 span: Span,
86 source_id: impl Into<String>,
87 source_text: Arc<str>,
88 doc_name: impl Into<String>,
89 doc_start_line: usize,
90 ) -> Self {
91 Self::Semantic(Box::new(ErrorDetails {
92 message: message.into(),
93 span,
94 source_id: source_id.into(),
95 source_text,
96 doc_name: doc_name.into(),
97 doc_start_line,
98 suggestion: None,
99 }))
100 }
101
102 pub fn semantic_with_suggestion(
104 message: impl Into<String>,
105 span: Span,
106 source_id: impl Into<String>,
107 source_text: Arc<str>,
108 doc_name: impl Into<String>,
109 doc_start_line: usize,
110 suggestion: impl Into<String>,
111 ) -> Self {
112 Self::Semantic(Box::new(ErrorDetails {
113 message: message.into(),
114 span,
115 source_id: source_id.into(),
116 source_text,
117 doc_name: doc_name.into(),
118 doc_start_line,
119 suggestion: Some(suggestion.into()),
120 }))
121 }
122}
123
124impl fmt::Display for LemmaError {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 LemmaError::Parse(details) => {
128 let context = ErrorContext {
129 error_type: "Parse error",
130 message: &details.message,
131 span: &details.span,
132 source_id: &details.source_id,
133 source_text: &details.source_text,
134 doc_name: &details.doc_name,
135 doc_start_line: details.doc_start_line,
136 suggestion: details.suggestion.as_deref(),
137 note: None,
138 };
139 format_error_with_ariadne(f, &context)
140 }
141 LemmaError::Semantic(details) => {
142 let context = ErrorContext {
143 error_type: "Semantic error",
144 message: &details.message,
145 span: &details.span,
146 source_id: &details.source_id,
147 source_text: &details.source_text,
148 doc_name: &details.doc_name,
149 doc_start_line: details.doc_start_line,
150 suggestion: details.suggestion.as_deref(),
151 note: None,
152 };
153 format_error_with_ariadne(f, &context)
154 }
155 LemmaError::Runtime(details) => {
156 let context = ErrorContext {
157 error_type: "Runtime error",
158 message: &details.message,
159 span: &details.span,
160 source_id: &details.source_id,
161 source_text: &details.source_text,
162 doc_name: &details.doc_name,
163 doc_start_line: details.doc_start_line,
164 suggestion: details.suggestion.as_deref(),
165 note: None,
166 };
167 format_error_with_ariadne(f, &context)
168 }
169 LemmaError::Engine(msg) => write!(f, "Engine error: {}", msg),
170 LemmaError::CircularDependency(msg) => write!(f, "Circular dependency: {}", msg),
171 LemmaError::MultipleErrors(errors) => {
172 writeln!(f, "Multiple errors occurred:")?;
173 for error in errors {
174 writeln!(f, "\n{}", error)?;
175 }
176 Ok(())
177 }
178 }
179 }
180}
181
182struct ErrorContext<'a> {
183 error_type: &'a str,
184 message: &'a str,
185 span: &'a Span,
186 source_id: &'a str,
187 source_text: &'a str,
188 doc_name: &'a str,
189 doc_start_line: usize,
190 suggestion: Option<&'a str>,
191 note: Option<&'a str>,
192}
193
194fn format_error_with_ariadne(f: &mut fmt::Formatter<'_>, context: &ErrorContext) -> fmt::Result {
195 let mut output = Vec::new();
196
197 let doc_line = if context.span.line >= context.doc_start_line {
198 context.span.line - context.doc_start_line + 1
199 } else {
200 context.span.line };
202
203 let enhanced_message = format!(
205 "{}: {} (in doc '{}' at line {}, file {}:{})",
206 context.error_type,
207 context.message,
208 context.doc_name,
209 doc_line,
210 context.source_id,
211 context.span.line
212 );
213
214 let mut report = Report::build(ReportKind::Error, context.source_id, context.span.start)
215 .with_message(enhanced_message)
216 .with_label(
217 Label::new((context.source_id, context.span.start..context.span.end))
218 .with_message("")
219 .with_color(Color::Red),
220 );
221
222 if let Some(help) = context.suggestion {
223 report = report.with_help(help);
224 }
225
226 if let Some(note_text) = context.note {
227 report = report.with_note(note_text);
228 }
229
230 match report.finish().write(
231 (context.source_id, Source::from(context.source_text)),
232 &mut output,
233 ) {
234 Ok(_) => write!(f, "{}", String::from_utf8_lossy(&output)),
235 Err(_) => {
236 write!(
238 f,
239 "{}: {} at {}:{}:{}",
240 context.error_type,
241 context.message,
242 context.source_id,
243 context.span.line,
244 context.span.col
245 )
246 }
247 }
248}
249
250impl std::error::Error for LemmaError {}
251
252impl From<std::fmt::Error> for LemmaError {
253 fn from(err: std::fmt::Error) -> Self {
254 LemmaError::Engine(format!("Format error: {}", err))
255 }
256}