use crate::frontend::source_registry::{SourceRegistry, SourceSlice}; use std::fmt; #[derive(Debug, Clone)] pub enum LoxError { LexicalError { source_slice: SourceSlice, message: String, }, RuntimeError { source_slice: SourceSlice, message: String, }, ParseError { source_slice: SourceSlice, message: String, }, IoError { message: String, }, TypeMismatch { source_slice: SourceSlice, expected: String, found: String, }, } /// Configuration for error display formatting #[derive(Clone, Debug)] pub struct ErrorDisplayConfig { /// Show colored output (for terminals that support it) pub colored: bool, /// Number of context lines to show before and after the error pub context_lines: usize, /// Show line numbers pub show_line_numbers: bool, /// Use Unicode characters for arrows and decorations pub use_unicode: bool, } impl Default for ErrorDisplayConfig { fn default() -> Self { Self { colored: true, context_lines: 2, show_line_numbers: true, use_unicode: true, } } } impl ErrorDisplayConfig { pub fn simple() -> Self { Self { colored: false, context_lines: 1, show_line_numbers: false, use_unicode: false, } } pub fn verbose() -> Self { Self { colored: true, context_lines: 3, show_line_numbers: true, use_unicode: true, } } } impl LoxError { pub fn get_message(&self) -> String { match self { LoxError::LexicalError { message, .. } => message.clone(), LoxError::RuntimeError { message, .. } => message.clone(), LoxError::ParseError { message, .. } => message.clone(), LoxError::IoError { message } => message.clone(), LoxError::TypeMismatch { expected, found, .. } => { format!("expected {}, found {}", expected, found) } } } pub fn get_source_slice(&self) -> Option { match self { LoxError::LexicalError { source_slice, .. } => Some(source_slice.clone()), LoxError::RuntimeError { source_slice, .. } => Some(source_slice.clone()), LoxError::ParseError { source_slice, .. } => Some(source_slice.clone()), LoxError::IoError { .. } => None, LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()), } } pub fn error_kind(&self) -> &'static str { match self { LoxError::LexicalError { .. } => "lexical error", LoxError::RuntimeError { .. } => "runtime error", LoxError::ParseError { .. } => "parse error", LoxError::IoError { .. } => "io error", LoxError::TypeMismatch { .. } => "type error", } } /// Display error with source code context using default configuration pub fn display_with_source(&self, source_registry: &SourceRegistry) -> String { self.display_with_source_and_config(source_registry, &ErrorDisplayConfig::default()) } /// Display error with source code context using custom configuration pub fn display_with_source_and_config( &self, source_registry: &SourceRegistry, config: &ErrorDisplayConfig, ) -> String { let mut output = String::new(); // Error header let header = if config.colored { format!( "\x1b[1;31merror\x1b[0m: [{}] {}", self.error_kind(), self.get_message() ) } else { format!("error: [{}] {}", self.error_kind(), self.get_message()) }; output.push_str(&header); output.push('\n'); // If we have source location, show it if let Some(source_slice) = self.get_source_slice() { let source_info = source_registry.get_by_id(source_slice.source_id); // File location line let location = if config.colored { format!( " \x1b[34m-->\x1b[0m {}:{}:{}", source_info.file_name, source_slice.start_position.line + 1, source_slice.start_position.column + 1 ) } else { format!( " --> {}:{}:{}", source_info.file_name, source_slice.start_position.line + 1, source_slice.start_position.column + 1 ) }; output.push_str(&location); output.push('\n'); // Source code context if let Some(source_context) = self.get_source_context(source_registry, &source_slice, config) { output.push_str(&source_context); } } output } fn get_source_context( &self, source_registry: &SourceRegistry, source_slice: &SourceSlice, config: &ErrorDisplayConfig, ) -> Option { let source_info = source_registry.get_by_id(source_slice.source_id); let lines: Vec<&str> = source_info.content.lines().collect(); if lines.is_empty() || source_slice.start_position.line >= lines.len() { return None; } let mut output = String::new(); let error_line = source_slice.start_position.line; let start_line = error_line.saturating_sub(config.context_lines); let end_line = (error_line + config.context_lines + 1).min(lines.len()); // Calculate the width needed for line numbers let line_number_width = if config.show_line_numbers { (end_line + 1).to_string().len() } else { 0 }; // Show context lines for line_idx in start_line..end_line { let line_content = lines[line_idx]; let line_number = line_idx + 1; if config.show_line_numbers { let line_num_str = if config.colored { if line_idx == error_line { format!( "\x1b[1;34m{:width$}\x1b[0m", line_number, width = line_number_width ) } else { format!( "\x1b[34m{:width$}\x1b[0m", line_number, width = line_number_width ) } } else { format!("{:width$}", line_number, width = line_number_width) }; let separator = if config.colored { if line_idx == error_line { "\x1b[1;34m |\x1b[0m " } else { "\x1b[34m |\x1b[0m " } } else { " | " }; output.push_str(&format!( " {}{}{}\n", line_num_str, separator, line_content )); } else { output.push_str(&format!(" {}\n", line_content)); } // Add error indicator on the error line if line_idx == error_line { let spaces_before = " ".repeat(3 + line_number_width + 3 + source_slice.start_position.column); let error_length = if source_slice.start_position.line == source_slice.end_position.line { (source_slice.end_position.column - source_slice.start_position.column).max(1) } else { line_content.len() - source_slice.start_position.column }; let indicator = if config.use_unicode { "^".repeat(error_length) } else { "^".repeat(error_length) }; let colored_indicator = if config.colored { format!("\x1b[1;31m{}\x1b[0m", indicator) } else { indicator }; // Add the indicator line if config.show_line_numbers { let empty_line_number = " ".repeat(line_number_width); let separator = if config.colored { "\x1b[34m |\x1b[0m " } else { " | " }; output.push_str(&format!( " {}{}{}{}\n", empty_line_number, separator, spaces_before[3 + line_number_width + 3..].to_string(), colored_indicator )); } else { output.push_str(&format!( " {}{}\n", spaces_before[3..].to_string(), colored_indicator )); } // Add error type and additional context let error_note = match self { LoxError::TypeMismatch { expected, found, .. } => Some(format!("expected `{}`, found `{}`", expected, found)), _ => None, }; if let Some(note) = error_note { let note_prefix = if config.show_line_numbers { format!( " {} {} ", " ".repeat(line_number_width), if config.colored { "\x1b[34m|\x1b[0m" } else { "|" } ) } else { " ".to_string() }; let colored_note = if config.colored { format!("\x1b[1;36mnote\x1b[0m: {}", note) } else { format!("note: {}", note) }; output.push_str(&format!("{}{}\n", note_prefix, colored_note)); } } } Some(output) } /// Print error with source context to stdout pub fn print_with_source(&self, source_registry: &SourceRegistry) { println!("{}", self.display_with_source(source_registry)); } /// Print error with source context and custom config to stdout pub fn print_with_source_and_config( &self, source_registry: &SourceRegistry, config: &ErrorDisplayConfig, ) { println!( "{}", self.display_with_source_and_config(source_registry, config) ); } /// Legacy method for backwards compatibility pub fn print_with_context(&self, source_registry: &SourceRegistry) { self.print_with_source(source_registry); } } impl fmt::Display for LoxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LoxError::LexicalError { source_slice, message, } => { write!( f, "Lexical error at {}:{}: {}", source_slice.start_position.line + 1, source_slice.start_position.column + 1, message ) } LoxError::RuntimeError { source_slice, message, } => { write!( f, "Runtime error at {}:{}: {}", source_slice.start_position.line + 1, source_slice.start_position.column + 1, message ) } LoxError::ParseError { source_slice, message, } => { write!( f, "Parse error at {}:{}: {}", source_slice.start_position.line + 1, source_slice.start_position.column + 1, message ) } LoxError::IoError { message } => write!(f, "IO error: {}", message), LoxError::TypeMismatch { source_slice, expected, found, } => { write!( f, "Type mismatch at {}:{}: expected `{}`, found `{}`", source_slice.start_position.line + 1, source_slice.start_position.column + 1, expected, found ) } } } } impl std::error::Error for LoxError {} pub type LoxResult = Result; /// Helper function to create a lexical error pub fn lexical_error(source_slice: SourceSlice, message: impl Into) -> LoxError { LoxError::LexicalError { source_slice, message: message.into(), } } /// Helper function to create a parse error pub fn parse_error(source_slice: SourceSlice, message: impl Into) -> LoxError { LoxError::ParseError { source_slice, message: message.into(), } } /// Helper function to create a runtime error pub fn runtime_error(source_slice: SourceSlice, message: impl Into) -> LoxError { LoxError::RuntimeError { source_slice, message: message.into(), } } /// Helper function to create a type mismatch error pub fn type_mismatch_error( source_slice: SourceSlice, expected: impl Into, found: impl Into, ) -> LoxError { LoxError::TypeMismatch { source_slice, expected: expected.into(), found: found.into(), } } /// Helper function to create an IO error pub fn io_error(message: impl Into) -> LoxError { LoxError::IoError { message: message.into(), } }