use std::fmt::Debug;
use colored::*;
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthStr;
use crate::parser::Source;
pub mod collector;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ReportSeverity {
Error,
Warning,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
pub span: (usize, usize), pub source: Source, }
pub trait Diagnostic: Debug + Send + Sync {
fn severity(&self) -> ReportSeverity;
fn title(&self) -> String;
fn message(&self) -> String;
fn location(&self) -> Option<SourceLocation>;
fn help(&self) -> Option<String>;
fn copy(&self) -> Box<dyn Diagnostic>;
fn format_report(&self) -> String {
let (title_color, primary_color) = match self.severity() {
ReportSeverity::Error => (Color::BrightRed, Color::BrightRed),
ReportSeverity::Warning => (Color::Yellow, Color::Yellow),
};
let mut report = String::new();
report.push_str(&format!(
"{}: {}",
self.title().color(title_color).bold(),
self.message()
));
if let Some(loc) = self.location() {
let source_content: String = loc.source.iter().collect();
let lines: Vec<&str> = source_content.lines().collect();
let (start_char, end_char) = loc.span;
let (start_line_idx, start_col_char) = find_line_and_col(&source_content, start_char);
let (end_line_idx, end_col_char) = find_line_and_col(&source_content, end_char);
match loc.source.file_path() {
Some(path) => {
report.push_str(&format!(
"\n {} {}:{} in {}\n",
"-->".bright_blue().bold(),
(start_line_idx + 1).to_string().bright_cyan(),
(start_col_char + 1).to_string().bright_cyan(),
path.display().to_string().bright_yellow().underline()
));
}
None => {
report.push_str(&format!(
"\n {} {}:{}\n",
"-->".bright_blue().bold(),
(start_line_idx + 1).to_string().bright_cyan(),
(start_col_char + 1).to_string().bright_cyan()
));
}
}
for i in start_line_idx..=end_line_idx {
if let Some(line_text) = lines.get(i) {
report.push_str(&format!(
" {:>4} {} {}\n",
(i + 1).to_string().bright_cyan(),
"|".bright_blue().bold(),
line_text.white()
));
let underline = build_underline(
i,
start_line_idx,
end_line_idx,
start_col_char,
end_col_char,
line_text,
);
report.push_str(&format!(
" {} {}\n",
"|".bright_blue().bold(),
underline.color(primary_color).bold()
));
}
}
} else {
report.push_str(&format!(
"\n{}\n",
"Note: Location information not available for this diagnostic.".italic()
));
}
if let Some(help_text) = self.help() {
report.push_str(&format!("\n{}: {}", "Help".bright_green(), help_text));
}
report
}
}
fn build_underline(
current_line_idx: usize,
start_line_idx: usize,
end_line_idx: usize,
start_col_char: usize,
end_col_char: usize,
line_text: &str,
) -> String {
if current_line_idx == start_line_idx && current_line_idx == end_line_idx {
let prefix_width = line_text
.chars()
.take(start_col_char)
.collect::<String>()
.width();
let error_width = if end_col_char > start_col_char {
line_text
.chars()
.skip(start_col_char)
.take(end_col_char - start_col_char)
.collect::<String>()
.width()
} else {
1
};
format!(
"{}{}",
" ".repeat(prefix_width),
"^".repeat(error_width.max(1))
)
} else if current_line_idx == start_line_idx {
let prefix_width = line_text
.chars()
.take(start_col_char)
.collect::<String>()
.width();
let error_width = line_text.width() - prefix_width;
format!(
"{}{}",
" ".repeat(prefix_width),
"^".repeat(error_width.max(1))
)
} else if current_line_idx == end_line_idx {
let error_width = line_text
.chars()
.take(end_col_char)
.collect::<String>()
.width();
"^".repeat(error_width.max(1)).to_string()
} else {
"^".repeat(line_text.width()).to_string()
}
}
fn find_line_and_col(source: &str, char_pos: usize) -> (usize, usize) {
let chars: Vec<char> = source.chars().collect();
if char_pos >= chars.len() {
let line_count = source.lines().count();
return (line_count.saturating_sub(1), 0);
}
let mut current_line = 0;
let mut current_col = 0;
for (i, &ch) in chars.iter().enumerate() {
if i == char_pos {
return (current_line, current_col);
}
if ch == '\n' {
current_line += 1;
current_col = 0;
} else if ch == '\r' {
if i + 1 < chars.len() && chars[i + 1] == '\n' {
continue;
} else {
current_line += 1;
current_col = 0;
}
} else {
current_col += 1;
}
}
(current_line, current_col)
}