use crate::{error::RoanError, span::TextSpan};
use colored::Colorize;
use log::Level;
use std::io::{BufWriter, Stderr, Write};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub title: String,
pub text: Option<String>,
pub level: Level,
pub location: Option<TextSpan>,
pub hint: Option<String>,
pub content: Option<String>,
}
impl Diagnostic {
pub fn log_pretty(&self, buff: &mut BufWriter<Stderr>) {
writeln!(
buff,
"{}{}{}",
self.level.to_string().to_lowercase().bright_red(),
": ".dimmed(),
self.title
)
.expect("Error writing level");
if let Some(location) = &self.location {
if let Some(content) = &self.content {
let line_number = location.start.line;
let line = content
.lines()
.nth((line_number - 1) as usize)
.unwrap_or("");
let column = location.start.column;
let line_content = line.trim_end();
let decoration =
"^".repeat(location.end.column as usize - location.start.column as usize);
writeln!(buff, "{} {}:{}", "--->".cyan(), line_number, column)
.expect("Error writing line number");
if line_number > 1 {
let line_before = format!("{} |", line_number - 1);
writeln!(buff, "{}", line_before.cyan()).expect("Error writing line number");
}
let line_current = format!("{} |", line_number);
write!(buff, "{}", line_current.cyan()).expect("Error writing line number");
writeln!(buff, " {}", line_content).expect("Error writing content");
let padding_left =
" ".repeat((column + 6 + line_number.to_string().len() as u32) as usize);
writeln!(buff, "{}{}", padding_left, decoration.bright_red())
.expect("Error writing decoration");
if line_number > 1 {
let line_after = format!("{} |", line_number + 1);
writeln!(buff, "{}", line_after.cyan()).expect("Error writing line number");
}
}
}
if let Some(text) = &self.text {
writeln!(buff, "{}", text).expect("Error writing text");
}
self.print_hint(buff);
}
pub fn print_hint(&self, buff: &mut BufWriter<Stderr>) {
if let Some(hint) = &self.hint {
writeln!(buff, "{}{}", "Hint: ".bright_cyan(), hint.bright_cyan())
.expect("Error writing hint");
}
}
}
pub fn print_diagnostic(err: anyhow::Error, content: Option<String>) {
let pulse_error = err.downcast_ref::<RoanError>();
if let Some(err) = pulse_error {
let err_str = err.to_string();
if let RoanError::Throw(content, frames) = err {
let mut buff = BufWriter::new(std::io::stderr());
write!(buff, "{}{}", "error".bright_red(), ": ".dimmed()).expect("Error writing level");
writeln!(buff, "{}", content).expect("Error writing text");
for frame in frames {
writeln!(buff, "{:?}", frame).expect("Error writing text");
}
return;
}
let diagnostic = match err {
RoanError::Io(_) => Diagnostic {
title: "IO error".to_string(),
text: Some(err_str),
level: Level::Error,
location: None,
hint: None,
content: None,
},
RoanError::RestParameterNotLast(span)
| RoanError::RestParameterNotLastPosition(span)
| RoanError::MultipleRestParameters(span)
| RoanError::SelfParameterCannotBeRest(span)
| RoanError::SelfParameterNotFirst(span)
| RoanError::MultipleSelfParameters(span)
| RoanError::StaticContext(span)
| RoanError::StaticMemberAccess(span)
| RoanError::StaticMemberAssignment(span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
RoanError::InvalidToken(_, span)
| RoanError::SemanticError(_, span)
| RoanError::UnexpectedToken(_, span)
| RoanError::InvalidEscapeSequence(_, span)
| RoanError::NonBooleanCondition(_, span)
| RoanError::StructNotFoundError(_, span)
| RoanError::TraitNotFoundError(_, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
RoanError::TraitMethodNotImplemented(name, methods, span) => Diagnostic {
title: format!(
"Trait {name} doesn't implement these methods: {}",
methods.join(", ")
),
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some("Method not implemented".to_string()),
content,
},
RoanError::StructAlreadyImplementsTrait(_, _, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some("Struct already implements this trait".to_string()),
content,
},
RoanError::ExpectedToken(expected, hint, span) => Diagnostic {
title: format!("Expected {}", expected),
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some(hint.clone()),
content,
},
RoanError::FailedToImportModule(_, _, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
RoanError::InvalidType(_, _, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
RoanError::ResolverError(_) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: None,
hint: None,
content: None,
},
RoanError::ModuleError(_) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: None,
hint: None,
content: None,
},
| RoanError::UndefinedFunctionError(_, span)
| RoanError::VariableNotFoundError(_, span)
| RoanError::ImportError(_, span)
| RoanError::PropertyNotFoundError(_, span)
| RoanError::TypeMismatch(_, span)
| RoanError::InvalidAssignment(_, span)
| RoanError::MissingParameter(_, span)
| RoanError::InvalidUnaryOperation(_, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
RoanError::InvalidBreakOrContinue(span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some(
"Break and continue statements can only be used inside loops".to_string(),
),
content,
},
RoanError::LoopBreak(span) | RoanError::LoopContinue(span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some(
"Break and continue statements can only be used inside loops".to_string(),
),
content,
},
RoanError::InvalidSpread(span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some(
"Spread operator can only be used in function calls or vectors".to_string(),
),
content,
},
RoanError::InvalidPropertyAccess(span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: Some("Only string literals or call expressions are allowed".to_string()),
content,
},
RoanError::IndexOutOfBounds(_, _, span) => Diagnostic {
title: err_str,
text: None,
level: Level::Error,
location: Some(span.clone()),
hint: None,
content,
},
_ => {
log::error!("{:?}", err);
return;
}
};
let mut buff = BufWriter::new(std::io::stderr());
diagnostic.log_pretty(&mut buff);
} else {
log::error!("{:?}", err);
}
}