Files
rlox/src/common/lox_result.rs
T

482 lines
15 KiB
Rust
Raw Normal View History

2025-11-12 20:45:35 +01:00
use crate::{
common::base_value::BaseValue,
frontend::source_registry::{SourceRegistry, SourceSlice},
};
use std::{error::Error, 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,
},
2026-02-11 16:35:05 +01:00
Return {
source_slice: SourceSlice,
value: BaseValue,
return_label: String,
},
}
2025-10-04 19:02:33 +02:00
/// 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)
}
2026-02-11 16:35:05 +01:00
LoxError::Return {
value,
return_label,
..
} => {
format!("return value: {} and label: {}", value, return_label)
}
}
}
pub fn get_source_slice(&self) -> Option<SourceSlice> {
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()),
2026-06-29 20:47:59 +02:00
LoxError::Return { .. } => None,
}
}
2025-10-04 19:02:33 +02:00
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",
2026-02-11 16:35:05 +01:00
LoxError::Return { .. } => "return error",
2025-10-04 19:02:33 +02:00
}
}
/// 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);
}
}
2025-10-04 19:02:33 +02:00
output
}
fn get_source_context(
&self,
source_registry: &SourceRegistry,
source_slice: &SourceSlice,
config: &ErrorDisplayConfig,
) -> Option<String> {
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);
}
}
2025-10-04 19:02:33 +02:00
impl fmt::Display for LoxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LoxError::LexicalError {
source_slice,
message,
} => {
2025-10-04 19:02:33 +02:00
write!(
f,
"Lexical error at {}:{}: {}",
source_slice.start_position.line + 1,
source_slice.start_position.column + 1,
message
)
}
LoxError::RuntimeError {
source_slice,
message,
} => {
2025-10-04 19:02:33 +02:00
write!(
f,
"Runtime error at {}:{}: {}",
source_slice.start_position.line + 1,
source_slice.start_position.column + 1,
message
)
}
LoxError::ParseError {
source_slice,
message,
} => {
2025-10-04 19:02:33 +02:00
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,
2025-10-04 19:02:33 +02:00
"Type mismatch at {}:{}: expected `{}`, found `{}`",
source_slice.start_position.line + 1,
source_slice.start_position.column + 1,
expected,
found
)
}
2026-02-11 16:35:05 +01:00
LoxError::Return {
value,
return_label,
source_slice,
} => {
write!(
f,
"Return error at {}:{}: value `{}`, label `{}`",
source_slice.start_position.line + 1,
source_slice.start_position.column + 1,
value,
return_label
)
}
}
}
}
2025-11-12 20:45:35 +01:00
impl Error for LoxError {}
pub type LoxResult<T> = Result<T, LoxError>;
2025-10-04 19:02:33 +02:00
/// Helper function to create a lexical error
2025-10-06 18:52:32 +02:00
#[allow(dead_code)]
pub fn lexical_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
Err(LoxError::LexicalError {
2025-10-04 19:02:33 +02:00
source_slice,
message: message.into(),
})
2025-10-04 19:02:33 +02:00
}
/// Helper function to create a parse error
2025-10-06 18:52:32 +02:00
#[allow(dead_code)]
pub fn parse_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
Err(LoxError::ParseError {
2025-10-04 19:02:33 +02:00
source_slice,
message: message.into(),
})
2025-10-04 19:02:33 +02:00
}
/// Helper function to create a runtime error
pub fn runtime_error<T>(source_slice: SourceSlice, message: impl Into<String>) -> LoxResult<T> {
Err(LoxError::RuntimeError {
2025-10-04 19:02:33 +02:00
source_slice,
message: message.into(),
})
2025-10-04 19:02:33 +02:00
}
/// Helper function to create a type mismatch error
#[allow(dead_code)]
pub fn type_mismatch_error<T>(
2025-10-04 19:02:33 +02:00
source_slice: SourceSlice,
expected: impl Into<String>,
found: impl Into<String>,
) -> LoxResult<T> {
Err(LoxError::TypeMismatch {
2025-10-04 19:02:33 +02:00
source_slice,
expected: expected.into(),
found: found.into(),
})
2025-10-04 19:02:33 +02:00
}
/// Helper function to create an IO error
#[allow(dead_code)]
pub fn io_error<T>(message: impl Into<String>) -> LoxResult<T> {
Err(LoxError::IoError {
2025-10-04 19:02:33 +02:00
message: message.into(),
})
2025-10-04 19:02:33 +02:00
}