Add source location tracking to AST nodes
The commit expands the source location tracking through the AST by: 1. Switching from raw Expr/Stmt nodes to AstNode wrappers containing source slices 2. Updating interpreter and parser to preserve location info through node traversal 3. Enhancing error reporting with richer source context information
This commit is contained in:
+358
-65
@@ -1,4 +1,5 @@
|
||||
use crate::frontend::source_registry::{SourcePosition, SourceRegistry, SourceSlice};
|
||||
use crate::frontend::source_registry::{SourceRegistry, SourceSlice};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LoxError {
|
||||
@@ -24,6 +25,50 @@ pub enum LoxError {
|
||||
},
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@@ -49,88 +94,289 @@ impl LoxError {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_with_context(&self, source_registry: &SourceRegistry) {
|
||||
match self.get_source_slice() {
|
||||
Some(source_slice) => {
|
||||
let source_code = source_registry.get_source_code(&source_slice);
|
||||
println!(
|
||||
"\n =============================================== \n DATA:{} \n CODE \n {} | {}",
|
||||
self, source_slice.start_position.line ,source_code
|
||||
);
|
||||
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);
|
||||
}
|
||||
None => println!("{}", self),
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_source_code(path: &str, start: SourcePosition, end: SourcePosition) -> LoxResult<String> {
|
||||
let source_code = std::fs::read_to_string(path).map_err(|e| LoxError::IoError {
|
||||
message: format!("Failed to read file: {}", e),
|
||||
})?;
|
||||
|
||||
let lines: Vec<&str> = source_code.split('\n').collect();
|
||||
|
||||
// Verifica bounds
|
||||
if start.line >= lines.len() || end.line >= lines.len() {
|
||||
return Err(LoxError::IoError {
|
||||
message: "Line number out of bounds".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut result = String::new();
|
||||
|
||||
if start.line == end.line {
|
||||
// Caso speciale: stessa riga
|
||||
let line = lines[start.line];
|
||||
if end.column <= line.len() && start.column <= end.column {
|
||||
result.push_str(&line[start.column..end.column]);
|
||||
}
|
||||
} else {
|
||||
// Caso generale: righe multiple
|
||||
|
||||
// Prima riga: da start.column alla fine della riga
|
||||
let first_line = lines[start.line];
|
||||
if start.column < first_line.len() {
|
||||
result.push_str(&first_line[start.column..]);
|
||||
}
|
||||
result.push('\n');
|
||||
|
||||
// Righe intermedie: complete
|
||||
for i in (start.line + 1)..end.line {
|
||||
result.push_str(lines[i]);
|
||||
result.push('\n');
|
||||
}
|
||||
|
||||
// Ultima riga: dall'inizio fino a end.column
|
||||
let last_line = lines[end.line];
|
||||
if end.column <= last_line.len() {
|
||||
result.push_str(&last_line[..end.column]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoxError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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 on {:?}: \n {}", 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 on {:?}: \n{}", 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 on {:?}: \n{}", 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 {
|
||||
@@ -140,8 +386,11 @@ impl std::fmt::Display for LoxError {
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Type mismatch on {:?}: \n expected {}, found {}",
|
||||
source_slice, expected, found
|
||||
"Type mismatch at {}:{}: expected `{}`, found `{}`",
|
||||
source_slice.start_position.line + 1,
|
||||
source_slice.start_position.column + 1,
|
||||
expected,
|
||||
found
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -151,3 +400,47 @@ impl std::fmt::Display for LoxError {
|
||||
impl std::error::Error for LoxError {}
|
||||
|
||||
pub type LoxResult<T> = Result<T, LoxError>;
|
||||
|
||||
/// Helper function to create a lexical error
|
||||
pub fn lexical_error(source_slice: SourceSlice, message: impl Into<String>) -> 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<String>) -> 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<String>) -> 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<String>,
|
||||
found: impl Into<String>,
|
||||
) -> 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<String>) -> LoxError {
|
||||
LoxError::IoError {
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user