1use sipha_core::{span::Span, traits::RuleId, traits::TokenKind};
4
5use crate::expected::Expected;
6
7type ErrorContextVec<R> = Vec<ErrorContext<R>>;
9
10#[derive(Clone, Debug)]
12pub struct ErrorContext<R: RuleId> {
13 pub rule_id: R,
15 pub span: Span,
17 pub message: String,
19}
20
21#[derive(Clone, Debug)]
23pub enum ParseError<K: TokenKind, R: RuleId = ()> {
24 UnexpectedToken {
26 kind: K,
27 span: Span,
28 rule_context: Option<R>,
29 #[allow(clippy::type_complexity)]
30 context_stack: ErrorContextVec<R>,
31 },
32 Expected {
34 expected: Expected<K>,
35 found: Option<K>,
36 span: Span,
37 rule_context: Option<R>,
38 #[allow(clippy::type_complexity)]
39 context_stack: ErrorContextVec<R>,
40 },
41 NoRule {
43 kind: K,
44 span: Span,
45 rule_context: Option<R>,
46 #[allow(clippy::type_complexity)]
47 context_stack: ErrorContextVec<R>,
48 },
49 NonAssociative {
51 kind: K,
52 span: Span,
53 rule_context: Option<R>,
54 #[allow(clippy::type_complexity)]
55 context_stack: ErrorContextVec<R>,
56 },
57 InvalidContext {
59 rule: &'static str,
60 span: Span,
61 rule_context: Option<R>,
62 #[allow(clippy::type_complexity)]
63 context_stack: ErrorContextVec<R>,
64 },
65 UnexpectedEof {
67 span: Span,
68 rule_context: Option<R>,
69 #[allow(clippy::type_complexity)]
70 context_stack: ErrorContextVec<R>,
71 },
72 RangeError {
74 min: usize,
75 max: usize,
76 found: usize,
77 span: Span,
78 rule_context: Option<R>,
79 #[allow(clippy::type_complexity)]
80 context_stack: ErrorContextVec<R>,
81 },
82 MultipleAlternatives {
84 #[allow(clippy::type_complexity)]
85 alternatives: Vec<ParseError<K, R>>,
86 span: Span,
87 rule_context: Option<R>,
88 #[allow(clippy::type_complexity)]
89 context_stack: ErrorContextVec<R>,
90 },
91}
92
93impl<K: TokenKind, R: RuleId> ParseError<K, R> {
94 #[cfg(feature = "diagnostics")]
98 #[must_use]
99 pub fn to_diagnostic(&self) -> crate::diagnostic::Diagnostic {
100 use crate::diagnostic::{Diagnostic, Level};
101
102 match self {
103 ParseError::UnexpectedToken {
104 kind,
105 span,
106 rule_context,
107 context_stack,
108 } => {
109 let message =
110 Self::add_rule_context(format!("Unexpected token {:?}", kind), *rule_context);
111 let hints = vec![
112 "Check if you're missing a token before this position".to_string(),
113 "Verify the syntax matches the expected grammar".to_string(),
114 ];
115
116 Diagnostic {
117 level: Level::Error,
118 message,
119 spans: vec![span.clone()],
120 notes: Self::build_notes_from_context(context_stack),
121 hints,
122 suggestions: vec![],
123 }
124 }
125 ParseError::Expected {
126 expected,
127 found,
128 span,
129 rule_context,
130 context_stack,
131 } => {
132 let expected_str = match expected {
133 Expected::Single(k) => format!("{:?}", k),
134 Expected::Set(ks) => ks
135 .iter()
136 .map(|k| format!("{:?}", k))
137 .collect::<Vec<_>>()
138 .join(", "),
139 Expected::Dynamic(v) => v
140 .iter()
141 .map(|k| format!("{:?}", k))
142 .collect::<Vec<_>>()
143 .join(", "),
144 Expected::Multiple(alternatives) => {
145 let mut parts = Vec::new();
146 for alt in alternatives {
147 match alt {
148 Expected::Single(k) => parts.push(format!("{:?}", k)),
149 Expected::Set(ks) => {
150 parts.extend(ks.iter().map(|k| format!("{:?}", k)));
151 }
152 Expected::Dynamic(v) => {
153 parts.extend(v.iter().map(|k| format!("{:?}", k)));
154 }
155 Expected::Multiple(_) => {
156 parts.push("...".to_string());
157 }
158 }
159 }
160 parts.join(", ")
161 }
162 };
163 let message = Self::add_rule_context(
164 format!("Expected {}, found {:?}", expected_str, found),
165 *rule_context,
166 );
167 let mut suggestions = Vec::new();
168
169 if let Some(found_token) = found {
170 if let Expected::Single(expected_token) = expected {
171 let found_str = format!("{:?}", found_token);
172 let expected_str = format!("{:?}", expected_token);
173
174 if Self::simple_similarity(&found_str, &expected_str) > 0.7 {
175 suggestions.push(format!("Did you mean `{}`?", expected_str));
176 }
177 } else if let Expected::Set(expected_tokens) = expected {
178 let found_str = format!("{:?}", found_token);
179 let mut best_match: Option<String> = None;
180 let mut best_score = 0.0;
181
182 for expected_token in expected_tokens.iter() {
183 let expected_str = format!("{:?}", expected_token);
184 let score = Self::simple_similarity(&found_str, &expected_str);
185 if score > best_score && score > 0.7 {
186 best_score = score;
187 best_match = Some(expected_str);
188 }
189 }
190
191 if let Some(matched) = best_match {
192 suggestions.push(format!("Did you mean `{}`?", matched));
193 }
194 }
195 }
196
197 Diagnostic {
198 level: Level::Error,
199 message,
200 spans: vec![span.clone()],
201 notes: Self::build_notes_from_context(context_stack),
202 hints: vec![],
203 suggestions,
204 }
205 }
206 ParseError::MultipleAlternatives {
207 alternatives,
208 span,
209 rule_context,
210 context_stack,
211 } => {
212 let mut messages = Vec::new();
213 let mut all_expected = Vec::new();
214
215 for err in alternatives {
216 #[cfg(feature = "diagnostics")]
217 {
218 let diag = err.to_diagnostic();
219 messages.push(diag.message);
220 }
221 if let ParseError::Expected { expected, .. } = err {
222 match expected {
223 Expected::Single(k) => all_expected.push(format!("{:?}", k)),
224 Expected::Set(ks) => {
225 all_expected.extend(ks.iter().map(|k| format!("{:?}", k)));
226 }
227 Expected::Dynamic(v) => {
228 all_expected.extend(v.iter().map(|k| format!("{:?}", k)));
229 }
230 Expected::Multiple(_) => {}
231 }
232 }
233 }
234
235 let expected_str = if all_expected.is_empty() {
236 "one of the alternatives".to_string()
237 } else {
238 format!("one of: {}", all_expected.join(", "))
239 };
240
241 let message = Self::add_rule_context(
242 format!("Expected {}, but all alternatives failed", expected_str),
243 *rule_context,
244 );
245
246 let mut all_notes = messages;
247 all_notes.extend(context_stack.iter().rev().map(|ctx| ctx.message.clone()));
248
249 Diagnostic {
250 level: Level::Error,
251 message,
252 spans: vec![span.clone()],
253 notes: all_notes,
254 hints: vec![],
255 suggestions: vec![],
256 }
257 }
258 ParseError::NoRule {
259 kind,
260 span,
261 rule_context,
262 context_stack,
263 } => {
264 let message = Self::add_rule_context(
265 format!("No Pratt rule registered for {:?}", kind),
266 *rule_context,
267 );
268 Diagnostic {
269 level: Level::Error,
270 message,
271 spans: vec![span.clone()],
272 notes: Self::build_notes_from_context(context_stack),
273 hints: vec![],
274 suggestions: vec![],
275 }
276 }
277 ParseError::NonAssociative {
278 kind,
279 span,
280 rule_context,
281 context_stack,
282 } => {
283 let message = Self::add_rule_context(
284 format!("Operator {:?} is non-associative", kind),
285 *rule_context,
286 );
287 Diagnostic {
288 level: Level::Error,
289 message,
290 spans: vec![span.clone()],
291 notes: Self::build_notes_from_context(context_stack),
292 hints: vec!["Non-associative operators cannot be chained".to_string()],
293 suggestions: vec!["Add parentheses to clarify precedence".to_string()],
294 }
295 }
296 ParseError::InvalidContext {
297 rule,
298 span,
299 rule_context,
300 context_stack,
301 } => {
302 let message = Self::add_rule_context(
303 format!("Rule {} is disabled in this context", rule),
304 *rule_context,
305 );
306 Diagnostic {
307 level: Level::Error,
308 message,
309 spans: vec![span.clone()],
310 notes: Self::build_notes_from_context(context_stack),
311 hints: vec![],
312 suggestions: vec![],
313 }
314 }
315 ParseError::UnexpectedEof {
316 span,
317 rule_context,
318 context_stack,
319 } => {
320 let message =
321 Self::add_rule_context("Unexpected end of input".to_string(), *rule_context);
322 let hints = vec![
323 "The input ended before the parser could complete".to_string(),
324 "Check if you're missing closing brackets, parentheses, or other delimiters"
325 .to_string(),
326 ];
327
328 let suggestions = vec!["Add the missing tokens to complete the parse".to_string()];
329
330 Diagnostic {
331 level: Level::Error,
332 message,
333 spans: vec![span.clone()],
334 notes: Self::build_notes_from_context(context_stack),
335 hints,
336 suggestions,
337 }
338 }
339 ParseError::RangeError {
340 min,
341 max,
342 found,
343 span,
344 rule_context,
345 context_stack,
346 } => {
347 let message = Self::add_rule_context(
348 format!(
349 "Expected between {} and {} repetitions, found {}",
350 min, max, found
351 ),
352 *rule_context,
353 );
354 Diagnostic {
355 level: Level::Error,
356 message,
357 spans: vec![span.clone()],
358 notes: Self::build_notes_from_context(context_stack),
359 hints: vec![],
360 suggestions: vec![],
361 }
362 }
363 }
364 }
365
366 #[allow(dead_code)]
368 fn build_notes_from_context(context_stack: &[ErrorContext<R>]) -> Vec<String> {
369 context_stack
370 .iter()
371 .rev()
372 .map(|ctx| ctx.message.clone())
373 .collect()
374 }
375
376 #[allow(dead_code)]
378 fn add_rule_context(message: String, rule_context: Option<R>) -> String {
379 if let Some(rule) = rule_context {
380 format!("While parsing {:?}, {}", rule, message)
381 } else {
382 message
383 }
384 }
385
386 #[cfg(feature = "diagnostics")]
389 fn simple_similarity(s1: &str, s2: &str) -> f64 {
390 if s1 == s2 {
391 return 1.0;
392 }
393 if s1.is_empty() || s2.is_empty() {
394 return 0.0;
395 }
396
397 let s1_lower = s1.to_lowercase();
398 let s2_lower = s2.to_lowercase();
399
400 if s1_lower == s2_lower {
401 return 0.95;
402 }
403
404 if s1_lower.starts_with(&s2_lower) || s2_lower.starts_with(&s1_lower) {
405 return 0.8;
406 }
407
408 if s1_lower.contains(&s2_lower) || s2_lower.contains(&s1_lower) {
409 return 0.7;
410 }
411
412 let distance = Self::levenshtein_distance(&s1_lower, &s2_lower);
413 let max_len = s1.len().max(s2.len());
414 if max_len == 0 {
415 return 0.0;
416 }
417 1.0 - (distance as f64 / max_len as f64)
418 }
419
420 #[cfg(feature = "diagnostics")]
422 fn levenshtein_distance(s1: &str, s2: &str) -> usize {
423 let s1_chars: Vec<char> = s1.chars().collect();
424 let s2_chars: Vec<char> = s2.chars().collect();
425 let s1_len = s1_chars.len();
426 let s2_len = s2_chars.len();
427
428 if s1_len == 0 {
429 return s2_len;
430 }
431 if s2_len == 0 {
432 return s1_len;
433 }
434
435 let mut matrix = vec![vec![0; s2_len + 1]; s1_len + 1];
436
437 for (i, row) in matrix.iter_mut().enumerate().take(s1_len + 1) {
438 row[0] = i;
439 }
440 for (j, val) in matrix[0].iter_mut().enumerate().take(s2_len + 1) {
441 *val = j;
442 }
443
444 for i in 1..=s1_len {
445 for j in 1..=s2_len {
446 let cost = if s1_chars[i - 1] == s2_chars[j - 1] {
447 0
448 } else {
449 1
450 };
451 matrix[i][j] = (matrix[i - 1][j] + 1)
452 .min(matrix[i][j - 1] + 1)
453 .min(matrix[i - 1][j - 1] + cost);
454 }
455 }
456
457 matrix[s1_len][s2_len]
458 }
459}