Add Elm-style error diagnostics

Implement beautiful, informative error messages inspired by Elm:
- Rich diagnostic rendering with source code snippets
- Colored output with proper underlines showing error locations
- Categorized error titles (Type Mismatch, Unknown Name, etc.)
- Contextual hints and suggestions for common errors
- Support for type errors, runtime errors, and parse errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 04:01:49 -05:00
parent 66132779cc
commit d37f0fb096
5 changed files with 703 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
//! Parser for the Lux language
use crate::ast::*;
use crate::diagnostics::{Diagnostic, Severity};
use crate::lexer::{LexError, Lexer, Token, TokenKind};
use std::fmt;
@@ -21,6 +22,63 @@ impl fmt::Display for ParseError {
}
}
impl ParseError {
/// Convert to a rich diagnostic for Elm-style error display
pub fn to_diagnostic(&self) -> Diagnostic {
let (title, hints) = categorize_parse_error(&self.message);
Diagnostic {
severity: Severity::Error,
title,
message: self.message.clone(),
span: self.span,
hints,
}
}
}
/// Categorize parse errors to provide better titles and hints
fn categorize_parse_error(message: &str) -> (String, Vec<String>) {
let message_lower = message.to_lowercase();
if message_lower.contains("unexpected") && message_lower.contains("expected") {
(
"Unexpected Token".to_string(),
vec!["Check for missing or misplaced punctuation.".to_string()],
)
} else if message_lower.contains("expected") && message_lower.contains("expression") {
(
"Missing Expression".to_string(),
vec!["An expression was expected here.".to_string()],
)
} else if message_lower.contains("expected") && message_lower.contains(":") {
(
"Missing Type Annotation".to_string(),
vec!["A type annotation is required here.".to_string()],
)
} else if message_lower.contains("unclosed") || message_lower.contains("unterminated") {
(
"Unclosed Delimiter".to_string(),
vec![
"Check for matching opening and closing brackets.".to_string(),
"Make sure all strings are properly closed with quotes.".to_string(),
],
)
} else if message_lower.contains("invalid") {
(
"Invalid Syntax".to_string(),
vec!["Check the syntax of this construct.".to_string()],
)
} else if message_lower.contains("identifier") {
(
"Invalid Identifier".to_string(),
vec!["Identifiers must start with a letter and contain only letters, numbers, and underscores.".to_string()],
)
} else {
("Parse Error".to_string(), vec![])
}
}
impl From<LexError> for ParseError {
fn from(err: LexError) -> Self {
ParseError {